From 7f7c3ca8520c9cfb387946ff8c57ab1fc62463b3 Mon Sep 17 00:00:00 2001
From: rhenck <richard.henck@iqb.hu-berlin.de>
Date: Mon, 14 Mar 2022 15:41:48 +0100
Subject: [PATCH] Add sanatization of cloze children

---
 projects/common/util/element.factory.ts       | 25 ++++++-
 .../common/util/unit-definition-sanitizer.ts  | 66 ++++++++++++-------
 2 files changed, 66 insertions(+), 25 deletions(-)

diff --git a/projects/common/util/element.factory.ts b/projects/common/util/element.factory.ts
index faaa9f80b..382ef5d07 100644
--- a/projects/common/util/element.factory.ts
+++ b/projects/common/util/element.factory.ts
@@ -36,6 +36,7 @@ import {
   UIElement, UIElementType, UIElementValue,
   VideoElement
 } from '../interfaces/elements';
+import { ClozeDocument, ClozeDocumentParagraph, ClozeDocumentParagraphPart } from '../interfaces/cloze';
 
 export abstract class ElementFactory {
   static createElement(element: Partial<UIElement>): UIElement {
@@ -238,7 +239,29 @@ export abstract class ElementFactory {
     return {
       ...ElementFactory.initElement({ width: 450, height: 200, ...element }),
       type: 'cloze',
-      document: element.document !== undefined ? element.document : { type: 'doc', content: [] },
+      document: element.document !== undefined ?
+        {
+          ...element.document,
+          content: element.document.content
+            .map((paragraph: ClozeDocumentParagraph) => ({
+              ...paragraph,
+              content: paragraph.content
+                .map((paraPart: ClozeDocumentParagraphPart) => (
+                  ['TextField', 'DropList', 'ToggleButton'].includes(paraPart.type) ?
+                    {
+                      ...paraPart,
+                      attrs: {
+                        ...paraPart.attrs,
+                        model: ElementFactory.createElement(paraPart.attrs!.model as InputElement)
+                      }
+                    } :
+                    {
+                      ...paraPart
+                    }
+                ))
+            }))
+        } as ClozeDocument :
+        { type: 'doc', content: [] },
       position: ElementFactory.initPositionProps(element.position),
       styling: {
         ...ElementFactory.initBasicStyles(element.styling),
diff --git a/projects/common/util/unit-definition-sanitizer.ts b/projects/common/util/unit-definition-sanitizer.ts
index fd99cff9c..5564fb8c9 100644
--- a/projects/common/util/unit-definition-sanitizer.ts
+++ b/projects/common/util/unit-definition-sanitizer.ts
@@ -17,6 +17,7 @@ import ToggleButtonExtension from '../tiptap-editor-extensions/toggle-button';
 import DropListExtension from '../tiptap-editor-extensions/drop-list';
 import TextFieldExtension from '../tiptap-editor-extensions/text-field';
 import { ClozeDocument, ClozeDocumentParagraph, ClozeDocumentParagraphPart } from '../interfaces/cloze';
+import { ClozeUtils } from './cloze';
 
 export abstract class UnitDefinitionSanitizer {
   private static expectedUnitVersion: [number, number, number] =
@@ -103,6 +104,7 @@ export abstract class UnitDefinitionSanitizer {
       .includes(newElement.type as string)) {
       newElement = UnitDefinitionSanitizer.handlePlusOne(newElement as InputElement);
     }
+
     return newElement as unknown as UIElement;
   }
 
@@ -185,31 +187,26 @@ export abstract class UnitDefinitionSanitizer {
   Afterwards element models are added to the JSON.
    */
   private static handleClozeElement(element: Record<string, UIElementValue>): ClozeElement {
-    if (!element.parts || !element.text) throw Error('Can\'t read Cloze Element');
-    const uiElementParts = (element.parts as any[])
-      .map((el: any) => el
-        .filter((el2: { type: string; }) => ['text-field', 'drop-list', 'toggle-button'].includes(el2.type)))
-      .flat();
+    if (!element.document && (!element.parts || !element.text)) throw Error('Can\'t read Cloze Element');
 
-    const replacedText = (element.text as string).replace(/\\i|\\z|\\r/g, (match: string) => {
-      switch (match) {
-        case '\\i':
-          return '<aspect-nodeview-text-field></aspect-nodeview-text-field>';
-        case '\\z':
-          return '<aspect-nodeview-drop-list></aspect-nodeview-drop-list>';
-        case '\\r':
-          return '<aspect-nodeview-toggle-button></aspect-nodeview-toggle-button>';
-        default:
-          throw Error('error in match');
-      }
-    });
+    // Version 2.0.0 needs to be sanatized as well because child elements were not sanatized before
+    if (UnitDefinitionSanitizer.unitDefinitionVersion && UnitDefinitionSanitizer.unitDefinitionVersion[0] >= 3) {
+      return element as ClozeElement;
+    }
 
-    const editor = new Editor({
-      extensions: [StarterKit, ToggleButtonExtension, DropListExtension, TextFieldExtension],
-      content: replacedText
-    });
-    const doc: { type: string, content: ClozeDocumentParagraph[] } =
-      editor.getJSON() as { type: string, content: ClozeDocumentParagraph[] };
+    let childElements: UIElement[];
+    let doc: ClozeDocument;
+
+    if (element.document) {
+      childElements = ClozeUtils.getClozeChildElements((element as ClozeElement).document);
+      doc = element.document as ClozeDocument;
+    } else {
+      childElements = (element.parts as any[])
+        .map((el: any) => el
+          .filter((el2: { type: string; }) => ['text-field', 'drop-list', 'toggle-button'].includes(el2.type)).value)
+        .flat();
+      doc = UnitDefinitionSanitizer.createClozeDocument(element);
+    }
 
     return {
       ...element,
@@ -225,7 +222,7 @@ export abstract class UnitDefinitionSanitizer {
                     ...paraPart,
                     attrs: {
                       ...paraPart.attrs,
-                      model: UnitDefinitionSanitizer.sanatizeElement(uiElementParts.shift().value)
+                      model: UnitDefinitionSanitizer.sanatizeElement(childElements.shift()!)
                     }
                   } :
                   {
@@ -237,6 +234,27 @@ export abstract class UnitDefinitionSanitizer {
     } as ClozeElement;
   }
 
+  private static createClozeDocument(element: Record<string, UIElementValue>): ClozeDocument {
+    const replacedText = (element.text as string).replace(/\\i|\\z|\\r/g, (match: string) => {
+      switch (match) {
+        case '\\i':
+          return '<aspect-nodeview-text-field></aspect-nodeview-text-field>';
+        case '\\z':
+          return '<aspect-nodeview-drop-list></aspect-nodeview-drop-list>';
+        case '\\r':
+          return '<aspect-nodeview-toggle-button></aspect-nodeview-toggle-button>';
+        default:
+          throw Error('error in match');
+      }
+    });
+
+    const editor = new Editor({
+      extensions: [StarterKit, ToggleButtonExtension, DropListExtension, TextFieldExtension],
+      content: replacedText
+    });
+    return editor.getJSON() as ClozeDocument;
+  }
+
   private static handleDropListElement(element: Record<string, UIElementValue>): DropListElement {
     const newElement = element;
     if (newElement.options) {
-- 
GitLab