diff --git a/backend/package.json b/backend/package.json
index 29c7ecbc..bacdac6c 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -24,7 +24,6 @@
"express": "^5.0.1",
"uuid": "^11.1.0",
"js-yaml": "^4.1.0",
- "@types/js-yaml": "^4.0.9"
},
"devDependencies": {
"@mikro-orm/cli": "^6.4.6",
diff --git a/backend/src/entities/content/learning-object.entity.ts b/backend/src/entities/content/learning-object.entity.ts
index aeee268d..4cf3f163 100644
--- a/backend/src/entities/content/learning-object.entity.ts
+++ b/backend/src/entities/content/learning-object.entity.ts
@@ -11,6 +11,7 @@ import {
import { Language } from './language.js';
import { Attachment } from './attachment.entity.js';
import { Teacher } from '../users/teacher.entity.js';
+import {DwengoContentType} from "../../services/learning-objects/processing/content_type";
@Entity()
export class LearningObject {
@@ -33,7 +34,7 @@ export class LearningObject {
description!: string;
@Property({ type: 'string' })
- contentType!: string;
+ contentType!: DwengoContentType;
@Property({ type: 'array' })
keywords: string[] = [];
@@ -95,12 +96,3 @@ export class ReturnValue {
@Property({ type: 'json' })
callbackSchema!: string;
}
-
-export enum ContentType {
- Markdown = 'text/markdown',
- Image = 'image/image',
- Mpeg = 'audio/mpeg',
- Pdf = 'application/pdf',
- Extern = 'extern',
- Blockly = 'Blockly',
-}
diff --git a/backend/src/services/learning-objects/processing/audio/audio_processor.js b/backend/src/services/learning-objects/processing/audio/audio_processor.js
new file mode 100644
index 00000000..d41ed1ce
--- /dev/null
+++ b/backend/src/services/learning-objects/processing/audio/audio_processor.js
@@ -0,0 +1,82 @@
+/**
+ * Based on
+ */
+import Processor from "../processor.ts";
+import { isValidHttpUrl } from '../../utils/utils.js'
+import { findFile } from '../../utils/file_io.js'
+import InvalidArgumentError from '../../utils/invalid_argument_error.js'
+import DOMPurify from 'isomorphic-dompurify';
+import ProcessingHistory from "../../models/processing_history.js";
+import path from "path"
+import fs from "fs"
+
+class AudioProcessor extends Processor {
+
+ constructor() {
+ super();
+ this.types = ["audio/mpeg"] // TODO add functionality to accept other audio types (ogg, wav)
+ }
+
+ /**
+ *
+ * @param {string} audioUrl
+ * @param {object} args Optional arguments specific to the render function of the AudioProcessor
+ * @returns
+ */
+ render(audioUrl, args = { files: [], metadata: {} }) {
+
+ if ((!args.files || args.files.length <= 0 || !findFile(audioUrl, args.files)) && !isValidHttpUrl(audioUrl)) {
+ if (args.metadata && args.metadata.hruid && args.metadata.version && args.metadata.language){
+ ProcessingHistory.error(args.metadata.hruid, args.metadata.version, args.metadata.language, "The audio file cannot be found. Please check if the url is spelled correctly.")
+ }else{
+ ProcessingHistory.error("generalError", "99999999", "en", "The audio file cannot be found. Please check if the url is spelled correctly.")
+ }
+
+ throw new InvalidArgumentError("The audio file cannot be found. Please check if the url is spelled correctly.");
+ }
+
+ let type;
+ if (!args.metadata || !args.metadata.content_type || !this.types.includes(args.metadata.content_type)) {
+ type = this.types[0];
+ } else {
+ type = args.metadata.content_type;
+ }
+
+ if (isValidHttpUrl(audioUrl)) {
+ return DOMPurify.sanitize(``);
+ }
+
+ if (!args.metadata._id) {
+ throw new InvalidArgumentError("The metadata for for the object which uses the file '" + audioUrl + "' is not loaded in the processor.");
+ }
+
+ return DOMPurify.sanitize(``);
+
+ }
+
+ processFiles(files, metadata){
+ let args = {}
+ let inputString = "";
+ let file = files.find((f) => {
+ let ext = path.extname(f.originalname);
+ if (ext == ".mp3") {
+ inputString = f["originalname"]
+ // add files to args to check if file exists
+ args.files = files;
+ args.metadata = metadata
+ return true;
+ }else{
+ return false;
+ }
+ });
+ return [this.render(inputString, args), files]
+ }
+}
+
+export default AudioProcessor;
diff --git a/backend/src/services/learning-objects/processing/blockly/blockly_processor.js b/backend/src/services/learning-objects/processing/blockly/blockly_processor.js
new file mode 100644
index 00000000..45296454
--- /dev/null
+++ b/backend/src/services/learning-objects/processing/blockly/blockly_processor.js
@@ -0,0 +1,83 @@
+import Processor from "../processor.ts";
+import { isValidHttpUrl } from '../../utils/utils.js'
+import InvalidArgumentError from '../../utils/invalid_argument_error.js'
+import DOMPurify from 'isomorphic-dompurify';
+import Logger from "../../logger.js";
+import path from "path"
+
+let logger = Logger.getLogger()
+class BlocklyProcessor extends Processor {
+ constructor() {
+ super();
+ this.blockly_base_url = process.env.SIMULATOR_READONLY_BASE_PATH;
+ }
+
+ /**
+ *
+ * @param {string} blocklyXml
+ * @param {object} args Optional arguments specific to the render function of the BlocklyProcessor
+ * @returns
+ */
+ render(blocklyXml, args = { language: "nl", id: "" }, {height = 315, aspect_ratio = 'iframe-16-9'} = {}) {
+ if (!args.language || args.language.trim() == "") {
+ args.language = "nl";
+ }
+ if (!args.id || args.id.trim() == "") {
+ throw new InvalidArgumentError("The unique object id must be passed to the blockly processor.");
+ }
+ let languages = ["aa", "ab", "af", "ak", "sq", "am", "ar", "an", "hy", "as", "av", "ae", "ay", "az", "ba", "bm", "eu", "be", "bn", "bh", "bi", "bs", "br", "bg", "my", "ca", "ch", "ce", "zh", "cu", "cv", "kw", "co", "cr", "cs", "da", "dv", "nl", "dz", "en", "eo", "et", "ee", "fo", "fj", "fi", "fr", "fy", "ff", "ka", "de", "gd", "ga", "gl", "gv", "el", "gn", "gu", "ht", "ha", "he", "hz", "hi", "ho", "hr", "hu", "ig", "is", "io", "ii", "iu", "ie", "ia", "id", "ik", "it", "jv", "ja", "kl", "kn", "ks", "kr", "kk", "km", "ki", "rw", "ky", "kv", "kg", "ko", "kj", "ku", "lo", "la", "lv", "li", "ln", "lt", "lb", "lu", "lg", "mk", "mh", "ml", "mi", "mr", "ms", "mg", "mt", "mn", "na", "nv", "nr", "nd", "ng", "ne", "nn", "nb", "no", "ny", "oc", "oj", "or", "om", "os", "pa", "fa", "pi", "pl", "pt", "ps", "qu", "rm", "ro", "rn", "ru", "sg", "sa", "si", "sk", "sl", "se", "sm", "sn", "sd", "so", "st", "es", "sc", "sr", "ss", "su", "sw", "sv", "ty", "ta", "tt", "te", "tg", "tl", "th", "bo", "ti", "to", "tn", "ts", "tk", "tr", "tw", "ug", "uk", "ur", "uz", "ve", "vi", "vo", "cy", "wa", "wo", "xh", "yi", "yo", "za", "zu"];
+ if (!languages.includes(args.language)) {
+ throw new InvalidArgumentError("The language must be valid. " + args.language + " is not a supported language.")
+ }
+ if (typeof blocklyXml == 'undefined') {
+ throw new InvalidArgumentError("The blockly XML is undefined. Please provide correct XML code.")
+ }
+
+ let simulatorUrl = `${this.blockly_base_url}`
+
+ let form = `
+
+ `
+
+ let iframe = `
+
+ `
+
+ let code = `(function(){
+ var auto = setTimeout(function(){ submitform(); }, 50);
+
+ function submitform(){
+ document.forms["blockly_form_${args.id}"].submit();
+ }
+ })()
+ `
+
+ let script = ``
+
+ let html = form + iframe // DOMPurify.sanitize(form + iframe, {ALLOW_UNKNOWN_PROTOCOLS: true, ADD_TAGS: ["iframe", "xml"], ADD_ATTR: ['allow', 'allowfullscreen', 'frameborder', 'scrolling', 'target']});
+ html = html + script;
+
+ return html; //TODO is not sanitized using DOMPurify.sanitize (problems with script tags)
+ }
+
+ processFiles(files, metadata){
+ let args = {}
+ let inputString = "";
+ let file = files.find((f) => {
+ let ext = path.extname(f.originalname);
+ if (ext == ".xml") {
+ inputString = f.buffer.toString('utf8');
+ args.language = metadata.language;
+ args.id = metadata._id;
+ return true;
+ }else{
+ return false;
+ }
+ });
+ return [this.render(inputString, args), files]
+ }
+}
+
+export default BlocklyProcessor;
diff --git a/backend/src/services/learning-objects/processing/content_type.ts b/backend/src/services/learning-objects/processing/content_type.ts
new file mode 100644
index 00000000..d71c97b4
--- /dev/null
+++ b/backend/src/services/learning-objects/processing/content_type.ts
@@ -0,0 +1,18 @@
+/**
+ * Based on https://github.com/dwengovzw/Learning-Object-Repository/blob/main/app/processors/content_type.js
+ */
+
+enum DwengoContentType {
+ TEXT_PLAIN = "text/plain",
+ TEXT_MARKDOWN = "text/markdown",
+ IMAGE_BLOCK = "image/image-block",
+ IMAGE_INLINE = "image/image",
+ AUDIO_MPEG = "audio/mpeg",
+ APPLICATION_PDF = "application/pdf",
+ EXTERN = "extern",
+ BLOCKLY = "blockly",
+ GIFT = "text/gift",
+ CT_SCHEMA = "text/ct-schema"
+}
+
+export { DwengoContentType }
diff --git a/backend/src/services/learning-objects/processing/ct_schema/ct_schema_processor.js b/backend/src/services/learning-objects/processing/ct_schema/ct_schema_processor.js
new file mode 100644
index 00000000..78c3dbc7
--- /dev/null
+++ b/backend/src/services/learning-objects/processing/ct_schema/ct_schema_processor.js
@@ -0,0 +1,67 @@
+import Logger from '../../logger.js';
+import InvalidArgumentError from "../../utils/invalid_argument_error.js";
+import { MarkdownProcessor } from '../markdown/markdown_processor.js';
+
+class CTSchemaProcessor extends MarkdownProcessor{
+ logger = Logger.getLogger();
+ constructor(args = { files: [], metadata: {} }) {
+ super();
+ this.staticPath = `${process.env.DOMAIN_URL}${process.env.STATIC_BASE_PATH}/img/ct_schema/`;
+ }
+
+ /**
+ *
+ * @param {string} mdText a string containing the content for the four ct schema in markdown
+ * @returns The sanitized version of the generated html.
+ */
+ render(text, args = {}) {
+ let html = "";
+ // 1. Split text into markdown parts for each CT aspect
+ // 2. Call super.render on the individual parts
+ // 3. Group the parts together with specific html structure
+
+ const regexObject = {
+ context: /([\s\S]*?)<\/context>/,
+ decomp: /([\s\S]*?)<\/decomposition>/,
+ abstr: /([\s\S]*?)<\/abstraction>/,
+ pattern: /([\s\S]*?)<\/patternRecognition>/,
+ algo: /([\s\S]*?)<\/algorithms>/,
+ impl: /([\s\S]*?)<\/implementation>/,
+ }
+
+ let htmlObject = {}
+
+ let htmlStructure = (valueObject) => `
+
+
${valueObject.context}
+
+
+
+
`
+
+ try {
+ for (let key in regexObject) {
+ let match = text.match(regexObject[key]);
+ if (match && match.length >= 1){
+ htmlObject[key] = super.render(match[1]);
+ }else{
+ htmlObject[key] = "";
+ }
+ }
+ } catch (e) {
+ throw new InvalidArgumentError(e.message);
+ return ""
+ }
+ return htmlStructure(htmlObject);
+ }
+
+}
+
+
+export { CTSchemaProcessor };
\ No newline at end of file
diff --git a/backend/src/services/learning-objects/processing/extern/extern_processor.js b/backend/src/services/learning-objects/processing/extern/extern_processor.js
new file mode 100644
index 00000000..b89ec401
--- /dev/null
+++ b/backend/src/services/learning-objects/processing/extern/extern_processor.js
@@ -0,0 +1,37 @@
+import Processor from "../processor.ts";
+import { isValidHttpUrl } from '../../utils/utils.js'
+import InvalidArgumentError from '../../utils/invalid_argument_error.js'
+import DOMPurify from 'isomorphic-dompurify';
+import Logger from "../../logger.js";
+
+let logger = Logger.getLogger()
+class ExternProcessor extends Processor {
+ constructor() {
+ super();
+ }
+
+ /**
+ *
+ * @param {string} externURL
+ * @param {object} args Optional arguments specific to the render function of the ExternProcessor
+ * @returns
+ */
+ render(externURL, {height = 315, aspect_ratio = 'iframe-16-9'} = {}) {
+ if (!isValidHttpUrl(externURL)) {
+ throw new InvalidArgumentError("The url is not valid: " + externURL);
+ }
+
+ // If a seperate youtube-processor would be added, this code would need to move to that processor
+ // Converts youtube urls to youtube-embed urls
+ let match = /(.*youtube.com\/)watch\?v=(.*)/.exec(externURL)
+ if (match) {
+ externURL = match[1] + "embed/" + match[2];
+ }
+
+
+ return DOMPurify.sanitize(``, { ADD_TAGS: ["iframe"], ADD_ATTR: ['allow', 'allowfullscreen', 'frameborder', 'scrolling']});
+
+ }
+}
+
+export default ExternProcessor;
diff --git a/backend/src/services/learning-objects/processing/gift/gift_processor.js b/backend/src/services/learning-objects/processing/gift/gift_processor.js
new file mode 100644
index 00000000..4825eb75
--- /dev/null
+++ b/backend/src/services/learning-objects/processing/gift/gift_processor.js
@@ -0,0 +1,52 @@
+import Processor from "../processor.ts";
+import { isValidHttpUrl } from '../../utils/utils.js'
+import { findFile } from '../../utils/file_io.js'
+import InvalidArgumentError from '../../utils/invalid_argument_error.js'
+import DOMPurify from 'isomorphic-dompurify';
+import ProcessingHistory from "../../models/processing_history.js";
+import path from "path"
+import fs from "fs"
+import { parse } from "gift-pegjs"
+
+class GiftProcessor extends Processor {
+
+ constructor() {
+ super();
+ this.types = ["text/gift"]
+ }
+
+ /**
+ *
+ * @param {string} audioUrl
+ * @param {object} args Optional arguments specific to the render function of the GiftProcessor
+ * @returns
+ */
+ render(giftString, args = { }) {
+
+ const quizQuestions = parse(giftString);
+ console.log(quizQuestions);
+
+
+ return DOMPurify.sanitize(`