From b873c052e0777df0435bd6ab24e6a79c3c3b14b5 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Fri, 11 May 2018 09:20:08 +0200
Subject: [PATCH] OO-3290: implement moving curriculum elements within a
 curriculum

---
 .../modules/curriculum/CurriculumElement.java |   3 +
 .../curriculum/CurriculumElementType.java     |   4 +
 .../modules/curriculum/CurriculumService.java |  14 +
 .../manager/CurriculumElementDAO.java         |  16 ++
 .../manager/CurriculumElementTypeDAO.java     |   6 +
 .../manager/CurriculumServiceImpl.java        |  13 +
 .../model/CurriculumElementImpl.java          |  14 +
 .../model/CurriculumElementTypeImpl.java      |   5 +-
 .../ui/CurriculumComposerController.java      |  26 +-
 .../curriculum/ui/CurriculumTreeModel.java    |  87 +++++++
 .../ui/MoveCurriculumElementController.java   | 245 ++++++++++++++++++
 .../ui/_content/move_curriculum_element.html  |   8 +
 .../database/mysql/alter_12_4_x_to_13_0_0.sql |   2 +
 .../database/mysql/setupDatabase.sql          |   2 +
 .../oracle/alter_12_4_x_to_13_0_0.sql         |   3 +
 .../database/oracle/setupDatabase.sql         |   3 +
 .../postgresql/alter_12_4_x_to_13_0_0.sql     |   4 +
 .../database/postgresql/setupDatabase.sql     |   3 +
 .../manager/CurriculumElementTypeDAOTest.java |  14 +
 19 files changed, 467 insertions(+), 5 deletions(-)
 create mode 100644 src/main/java/org/olat/modules/curriculum/ui/CurriculumTreeModel.java
 create mode 100644 src/main/java/org/olat/modules/curriculum/ui/MoveCurriculumElementController.java
 create mode 100644 src/main/java/org/olat/modules/curriculum/ui/_content/move_curriculum_element.html

diff --git a/src/main/java/org/olat/modules/curriculum/CurriculumElement.java b/src/main/java/org/olat/modules/curriculum/CurriculumElement.java
index 9338b8e489a..d0240145093 100644
--- a/src/main/java/org/olat/modules/curriculum/CurriculumElement.java
+++ b/src/main/java/org/olat/modules/curriculum/CurriculumElement.java
@@ -55,6 +55,9 @@ public interface CurriculumElement extends CurriculumElementRef, CreateInfo, Mod
 	
 	public CurriculumElement getParent();
 	
+	public CurriculumElementType getType();
+	
 	public Group getGroup();
+	
 
 }
diff --git a/src/main/java/org/olat/modules/curriculum/CurriculumElementType.java b/src/main/java/org/olat/modules/curriculum/CurriculumElementType.java
index 48c4714e086..81eb55a87c1 100644
--- a/src/main/java/org/olat/modules/curriculum/CurriculumElementType.java
+++ b/src/main/java/org/olat/modules/curriculum/CurriculumElementType.java
@@ -19,6 +19,8 @@
  */
 package org.olat.modules.curriculum;
 
+import java.util.Set;
+
 import org.olat.core.id.CreateInfo;
 import org.olat.core.id.ModifiedInfo;
 
@@ -45,5 +47,7 @@ public interface CurriculumElementType extends CurriculumElementTypeRef, CreateI
 	public String getExternalId();
 	
 	public void setExternalId(String externalId);
+	
+	public Set<CurriculumElementTypeToType> getAllowedSubTypes();
 
 }
diff --git a/src/main/java/org/olat/modules/curriculum/CurriculumService.java b/src/main/java/org/olat/modules/curriculum/CurriculumService.java
index e565e15b1ac..6e600edadf5 100644
--- a/src/main/java/org/olat/modules/curriculum/CurriculumService.java
+++ b/src/main/java/org/olat/modules/curriculum/CurriculumService.java
@@ -53,6 +53,13 @@ public interface CurriculumService {
 	
 	public List<Curriculum> getCurriculums(CurriculumSearchParameters params);
 	
+	/**
+	 * The list of all types available.
+	 * 
+	 * @return A list of curriculum element types
+	 */
+	public List<CurriculumElementType> getCurriculumElementTypes();
+	
 	
 	
 	public CurriculumElement createCurriculumElement(String identifier, String displayName,
@@ -70,6 +77,13 @@ public interface CurriculumService {
 	
 	public CurriculumElement updateCurriculumElement(CurriculumElement element);
 	
+	/**
+	 * 
+	 * @param elementToMove The element to move
+	 * @param newParent The new parent or null if root
+	 */
+	public CurriculumElement moveCurriculumElement(CurriculumElement elementToMove, CurriculumElement newParent);
+	
 	/**
 	 * The list of members of the specified curriculum element.
 	 * 
diff --git a/src/main/java/org/olat/modules/curriculum/manager/CurriculumElementDAO.java b/src/main/java/org/olat/modules/curriculum/manager/CurriculumElementDAO.java
index 2e29e1d998b..6c96cb3f1e7 100644
--- a/src/main/java/org/olat/modules/curriculum/manager/CurriculumElementDAO.java
+++ b/src/main/java/org/olat/modules/curriculum/manager/CurriculumElementDAO.java
@@ -89,6 +89,22 @@ public class CurriculumElementDAO {
 		return dbInstance.getCurrentEntityManager().merge(element);
 	}
 	
+	public CurriculumElement move(CurriculumElement element, CurriculumElement newParentElement) {
+		CurriculumElement parentLevel = element.getParent();
+		if(parentLevel == null && newParentElement == null) {
+			return element;//already root
+		} else if(parentLevel != null && parentLevel.equals(newParentElement)) {
+			return element;//same parent
+		}
+
+		CurriculumElementImpl elementImpl = (CurriculumElementImpl)element;
+		elementImpl.setParent(newParentElement);
+		elementImpl.setLastModified(new Date());
+		elementImpl = dbInstance.getCurrentEntityManager().merge(elementImpl);
+		dbInstance.commit();
+		return elementImpl;
+	}
+	
 	public List<CurriculumElement> loadElements(CurriculumRef curriculum) {
 		StringBuilder sb = new StringBuilder(256);
 		sb.append("select el from curriculumelement el")
diff --git a/src/main/java/org/olat/modules/curriculum/manager/CurriculumElementTypeDAO.java b/src/main/java/org/olat/modules/curriculum/manager/CurriculumElementTypeDAO.java
index 5bdfad96963..99b6406ded9 100644
--- a/src/main/java/org/olat/modules/curriculum/manager/CurriculumElementTypeDAO.java
+++ b/src/main/java/org/olat/modules/curriculum/manager/CurriculumElementTypeDAO.java
@@ -60,6 +60,12 @@ public class CurriculumElementTypeDAO {
 		return types == null || types.isEmpty() ? null : types.get(0);
 	}
 	
+	public List<CurriculumElementType> load() {
+		return dbInstance.getCurrentEntityManager()
+			.createNamedQuery("loadCurriculumElementTypes", CurriculumElementType.class)
+			.getResultList();
+	}
+	
 	public CurriculumElementType update(CurriculumElementType type) {
 		((CurriculumElementTypeImpl)type).setLastModified(new Date());
 		return dbInstance.getCurrentEntityManager().merge(type);
diff --git a/src/main/java/org/olat/modules/curriculum/manager/CurriculumServiceImpl.java b/src/main/java/org/olat/modules/curriculum/manager/CurriculumServiceImpl.java
index d8cc0bef5d8..84d364b3fa7 100644
--- a/src/main/java/org/olat/modules/curriculum/manager/CurriculumServiceImpl.java
+++ b/src/main/java/org/olat/modules/curriculum/manager/CurriculumServiceImpl.java
@@ -29,6 +29,7 @@ import org.olat.core.id.Organisation;
 import org.olat.modules.curriculum.Curriculum;
 import org.olat.modules.curriculum.CurriculumElement;
 import org.olat.modules.curriculum.CurriculumElementRef;
+import org.olat.modules.curriculum.CurriculumElementType;
 import org.olat.modules.curriculum.CurriculumRef;
 import org.olat.modules.curriculum.CurriculumRoles;
 import org.olat.modules.curriculum.CurriculumService;
@@ -59,6 +60,8 @@ public class CurriculumServiceImpl implements CurriculumService {
 	@Autowired
 	private CurriculumElementDAO curriculumElementDao;
 	@Autowired
+	private CurriculumElementTypeDAO curriculumElementTypeDao;
+	@Autowired
 	private RepositoryEntryRelationDAO repositoryEntryRelationDao;
 	@Autowired
 	private CurriculumRepositoryEntryRelationDAO curriculumRepositoryEntryRelationDao;
@@ -78,6 +81,11 @@ public class CurriculumServiceImpl implements CurriculumService {
 		return curriculumDao.update(curriculum);
 	}
 
+	@Override
+	public List<CurriculumElementType> getCurriculumElementTypes() {
+		return curriculumElementTypeDao.load();
+	}
+
 	@Override
 	public List<Curriculum> getCurriculums(CurriculumSearchParameters params) {
 		return curriculumDao.search(params);
@@ -99,6 +107,11 @@ public class CurriculumServiceImpl implements CurriculumService {
 		return curriculumElementDao.update(element);
 	}
 
+	@Override
+	public CurriculumElement moveCurriculumElement(CurriculumElement elementToMove, CurriculumElement newParent) {
+		return curriculumElementDao.move(elementToMove, newParent);
+	}
+
 	@Override
 	public List<CurriculumElement> getCurriculumElements(CurriculumRef curriculum) {
 		return curriculumElementDao.loadElements(curriculum);
diff --git a/src/main/java/org/olat/modules/curriculum/model/CurriculumElementImpl.java b/src/main/java/org/olat/modules/curriculum/model/CurriculumElementImpl.java
index dfda9261eaf..c57a7e1317b 100644
--- a/src/main/java/org/olat/modules/curriculum/model/CurriculumElementImpl.java
+++ b/src/main/java/org/olat/modules/curriculum/model/CurriculumElementImpl.java
@@ -44,6 +44,7 @@ import org.olat.core.id.Persistable;
 import org.olat.modules.curriculum.Curriculum;
 import org.olat.modules.curriculum.CurriculumElement;
 import org.olat.modules.curriculum.CurriculumElementManagedFlag;
+import org.olat.modules.curriculum.CurriculumElementType;
 
 /**
  * 
@@ -107,6 +108,10 @@ public class CurriculumElementImpl implements CurriculumElement, Persistable {
 	@JoinColumn(name="fk_curriculum", nullable=true, insertable=true, updatable=true)
 	private Curriculum curriculum;
 	
+	@ManyToOne(targetEntity=CurriculumElementTypeImpl.class,fetch=FetchType.LAZY,optional=true)
+	@JoinColumn(name="fk_type", nullable=true, insertable=true, updatable=true)
+	private CurriculumElementType type;
+	
 	@Override
 	public Long getKey() {
 		return key;
@@ -247,6 +252,15 @@ public class CurriculumElementImpl implements CurriculumElement, Persistable {
 		this.curriculum = curriculum;
 	}
 
+	@Override
+	public CurriculumElementType getType() {
+		return type;
+	}
+	
+	public void setType(CurriculumElementType type) {
+		this.type = type;
+	}
+
 	@Override
 	public int hashCode() {
 		return key == null ? 28562153 : key.hashCode();
diff --git a/src/main/java/org/olat/modules/curriculum/model/CurriculumElementTypeImpl.java b/src/main/java/org/olat/modules/curriculum/model/CurriculumElementTypeImpl.java
index f60f4007623..e140bba9c1c 100644
--- a/src/main/java/org/olat/modules/curriculum/model/CurriculumElementTypeImpl.java
+++ b/src/main/java/org/olat/modules/curriculum/model/CurriculumElementTypeImpl.java
@@ -48,9 +48,8 @@ import org.olat.modules.curriculum.CurriculumElementTypeToType;
  *
  */
 @NamedQueries({
-	@NamedQuery(name="loadCurriculumElementTypeByKey", query="select el from curriculumelementtype el where el.key=:key")
-	
-	
+	@NamedQuery(name="loadCurriculumElementTypeByKey", query="select el from curriculumelementtype el where el.key=:key"),
+	@NamedQuery(name="loadCurriculumElementTypes", query="select elementType from curriculumelementtype elementType")	
 })
 @Entity(name="curriculumelementtype")
 @Table(name="o_cur_element_type")
diff --git a/src/main/java/org/olat/modules/curriculum/ui/CurriculumComposerController.java b/src/main/java/org/olat/modules/curriculum/ui/CurriculumComposerController.java
index 6a8b9c5d46f..1ff6b07ed04 100644
--- a/src/main/java/org/olat/modules/curriculum/ui/CurriculumComposerController.java
+++ b/src/main/java/org/olat/modules/curriculum/ui/CurriculumComposerController.java
@@ -77,6 +77,7 @@ public class CurriculumComposerController extends FormBasicController implements
 	private EditCurriculumElementController newElementCtrl;
 	private CloseableCalloutWindowController toolsCalloutCtrl;
 	private EditCurriculumElementController newSubElementCtrl;
+	private MoveCurriculumElementController moveElementCtrl;
 	
 	private int counter;
 	private final Curriculum curriculum;
@@ -164,7 +165,7 @@ public class CurriculumComposerController extends FormBasicController implements
 
 	@Override
 	protected void event(UserRequest ureq, Controller source, Event event) {
-		if(newElementCtrl == source || newSubElementCtrl == source) {
+		if(newElementCtrl == source || newSubElementCtrl == source || moveElementCtrl == source) {
 			if(event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
 				loadModel();
 			}
@@ -177,8 +178,10 @@ public class CurriculumComposerController extends FormBasicController implements
 	}
 	
 	private void cleanUp() {
+		removeAsListenerAndDispose(moveElementCtrl);
 		removeAsListenerAndDispose(newElementCtrl);
 		removeAsListenerAndDispose(cmc);
+		moveElementCtrl = null;
 		newElementCtrl = null;
 		cmc = null;
 	}
@@ -257,6 +260,22 @@ public class CurriculumComposerController extends FormBasicController implements
 		}
 	}
 	
+	private void doMoveCurriculumElement(UserRequest ureq, CurriculumElementRow row) {
+		CurriculumElement element = curriculumService.getCurriculumElement(row);
+		if(element == null) {
+			tableEl.reloadData();
+			showWarning("warning.curriculum.element.deleted");
+		} else {
+			List<CurriculumElement> elementsToMove = Collections.singletonList(element);
+			moveElementCtrl = new MoveCurriculumElementController(ureq, getWindowControl(), elementsToMove, curriculum);
+			listenTo(moveElementCtrl);
+			
+			cmc = new CloseableModalController(getWindowControl(), "close", moveElementCtrl.getInitialComponent(), true, translate("add.curriculum.element"));
+			listenTo(cmc);
+			cmc.activate();
+		}
+	}
+	
 	private void doOpenTools(UserRequest ureq, CurriculumElementRow row, FormLink link) {
 		removeAsListenerAndDispose(toolsCtrl);
 		removeAsListenerAndDispose(toolsCalloutCtrl);
@@ -330,7 +349,10 @@ public class CurriculumComposerController extends FormBasicController implements
 			if(editLink == source) {
 				close();
 				doEditCurriculumElement(ureq, row);
-			} else if(deleteLink == source || moveLink == source) {
+			} else if(moveLink == source) {
+				close();
+				doMoveCurriculumElement(ureq, row);
+			} else if(deleteLink == source) {
 				close();
 				showWarning("Not implemented");
 			} else if(newLink == source) {
diff --git a/src/main/java/org/olat/modules/curriculum/ui/CurriculumTreeModel.java b/src/main/java/org/olat/modules/curriculum/ui/CurriculumTreeModel.java
new file mode 100644
index 00000000000..4bd5b926e36
--- /dev/null
+++ b/src/main/java/org/olat/modules/curriculum/ui/CurriculumTreeModel.java
@@ -0,0 +1,87 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.curriculum.ui;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.olat.core.gui.components.tree.GenericTreeModel;
+import org.olat.core.gui.components.tree.GenericTreeNode;
+import org.olat.modules.curriculum.CurriculumElement;
+import org.olat.modules.curriculum.CurriculumElementRef;
+
+/**
+ * 
+ * Initial date: 11 mai 2018<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CurriculumTreeModel extends GenericTreeModel {
+
+	private static final long serialVersionUID = 2911319509933144413L;
+
+	public static final String LEVEL_PREFIX = "cur-el-lev-";
+
+	public CurriculumTreeModel() {
+		GenericTreeNode root = new GenericTreeNode();
+		root.setTitle("ROOT");
+		setRootNode(root);
+	}
+	
+	public void loadTreeModel(List<CurriculumElement> elements) {
+		Map<Long,GenericTreeNode> fieldKeyToNode = new HashMap<>();
+		for(CurriculumElement element:elements) {
+			Long key = element.getKey();
+			GenericTreeNode node = fieldKeyToNode.computeIfAbsent(key, k -> {
+				GenericTreeNode newNode = new GenericTreeNode(nodeKey(element));
+				newNode.setTitle(element.getDisplayName());
+				newNode.setIconCssClass("o_icon_curriculum_element");
+				newNode.setUserObject(element);
+				return newNode;
+			});
+
+			CurriculumElement parentElement = element.getParent();
+			if(parentElement == null) {
+				//this is a root
+				getRootNode().addChild(node);
+			} else {
+				Long parentKey = parentElement.getKey();
+				GenericTreeNode parentNode = fieldKeyToNode.computeIfAbsent(parentKey, k -> {
+					GenericTreeNode newNode = new GenericTreeNode(nodeKey(parentElement));
+					newNode.setTitle(parentElement.getDisplayName());
+					newNode.setIconCssClass("o_icon_curriculum_element");
+					newNode.setUserObject(parentElement);
+					return newNode;
+				});
+				
+				if(parentNode == null) {
+					fieldKeyToNode.put(parentKey, parentNode);
+				} else {
+					parentNode.addChild(node);
+				}
+			}
+		}
+	}
+	
+	public static final String nodeKey(CurriculumElementRef element) {
+		return LEVEL_PREFIX + element.getKey();
+	}
+}
diff --git a/src/main/java/org/olat/modules/curriculum/ui/MoveCurriculumElementController.java b/src/main/java/org/olat/modules/curriculum/ui/MoveCurriculumElementController.java
new file mode 100644
index 00000000000..8a1b5d6ea65
--- /dev/null
+++ b/src/main/java/org/olat/modules/curriculum/ui/MoveCurriculumElementController.java
@@ -0,0 +1,245 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.curriculum.ui;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.tree.GenericTreeNode;
+import org.olat.core.gui.components.tree.MenuTreeItem;
+import org.olat.core.gui.components.tree.TreeNode;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.util.nodes.INode;
+import org.olat.modules.curriculum.Curriculum;
+import org.olat.modules.curriculum.CurriculumElement;
+import org.olat.modules.curriculum.CurriculumElementType;
+import org.olat.modules.curriculum.CurriculumElementTypeToType;
+import org.olat.modules.curriculum.CurriculumService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 8 déc. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class MoveCurriculumElementController extends FormBasicController {
+	
+	private MenuTreeItem curriculumTreeEl;
+	private final CurriculumTreeModel curriculumModel = new CurriculumTreeModel();
+	
+	private final Curriculum curriculum;
+	private Set<CurriculumElementType> allowedTypes;
+	private List<CurriculumElement> curriculumElementsToMove;
+	private Set<TreeNode> targetableNodes = new HashSet<>();
+	
+	@Autowired
+	private CurriculumService curriculumService;
+	
+	public MoveCurriculumElementController(UserRequest ureq, WindowControl wControl,
+			List<CurriculumElement> curriculumElementsToMove, Curriculum curriculum) {
+		super(ureq, wControl, "move_curriculum_element");
+		this.curriculum = curriculum;
+		this.curriculumElementsToMove = new ArrayList<>(curriculumElementsToMove);
+		allowedTypes = getAllowedTypes();
+		
+		initForm(ureq);
+		loadModel();
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		
+		curriculumTreeEl = uifactory.addTreeMultiselect("elements", null, formLayout, curriculumModel, this);
+		curriculumTreeEl.setMultiSelect(false);
+		curriculumTreeEl.setRootVisible(false);
+
+		uifactory.addFormCancelButton("cancel", formLayout, ureq, getWindowControl());
+		uifactory.addFormSubmitButton("move.element", formLayout);
+	}
+	
+	private void loadModel() {
+		List<CurriculumElement> allElements = curriculumService.getCurriculumElements(curriculum);
+		curriculumModel.loadTreeModel(allElements);
+		
+		//remove children of the curriculum element to move
+		for(CurriculumElement elementToMove:curriculumElementsToMove) {
+			TreeNode nodeToMove = curriculumModel
+					.getNodeById(CurriculumTreeModel.nodeKey(elementToMove));
+			nodeToMove.removeAllChildren();
+			if(nodeToMove.getParent() != null) {
+				nodeToMove.getParent().remove(nodeToMove);
+			}
+		}
+		
+		// remove the elements with incompatible types
+		List<TreeNode> openedNodes = new ArrayList<>();
+		filterByAllowedTypes(curriculumModel.getRootNode(), openedNodes);
+
+		List<String> nodeIds = openedNodes
+				.stream().map(TreeNode::getIdent)
+				.collect(Collectors.toList());
+		curriculumTreeEl.setOpenNodeIds(nodeIds);
+	}
+	
+	private boolean filterByAllowedTypes(TreeNode node, List<TreeNode> openedNodes) {
+		((GenericTreeNode)node).setIconCssClass(null);
+		
+		for(int i=node.getChildCount(); i-->0; ) {
+			boolean ok = filterByAllowedTypes((TreeNode)node.getChildAt(i), openedNodes);
+			if(!ok) {
+				node.remove(node.getChildAt(i));
+			}
+		}
+		
+		boolean ok = false;
+		Object uobject = node.getUserObject();
+		if(uobject instanceof CurriculumElement) {
+			CurriculumElement level = (CurriculumElement)uobject;
+			CurriculumElementType type = level.getType();
+			if(type == null || allowedTypes.contains(type)) {
+				openedNodes.add(node);
+				((GenericTreeNode)node).setIconCssClass("o_icon_node_under o_icon-rotate-180");
+				targetableNodes.add(node);
+				ok = true;
+			} else if(node.getChildCount() > 0) {
+				openedNodes.add(node);
+				ok = true;
+			}
+		} else {
+			targetableNodes.add(node);
+			openedNodes.add(node);
+			ok = true;
+		}
+
+		return ok;
+	}
+	
+	private Set<CurriculumElementType> getAllowedTypes() {
+		List<CurriculumElementType> allTypes = new ArrayList<>(curriculumService.getCurriculumElementTypes());
+		Map<CurriculumElementType, Set<CurriculumElementType>> subToParentTypes = new HashMap<>();
+		for(CurriculumElementType type:allTypes) {
+			Set<CurriculumElementTypeToType> typesToTypes = type.getAllowedSubTypes();
+			for(CurriculumElementTypeToType typeToType:typesToTypes) {
+				CurriculumElementType subTyp = typeToType.getAllowedSubType();
+				subToParentTypes
+					.computeIfAbsent(subTyp, t -> new HashSet<>())
+					.add(type);
+			}
+		}
+		
+		Set<CurriculumElementType> analyzedTypes = new HashSet<>();
+		for(CurriculumElement element:curriculumElementsToMove) {
+			CurriculumElementType levelType = element.getType();
+			if(levelType != null && !analyzedTypes.contains(levelType)) {
+				analyzedTypes.add(levelType);
+				
+				Set<CurriculumElementType> allowed = subToParentTypes.get(levelType);
+				if(allowed != null) {
+					allTypes.retainAll(allowed);
+				}
+			}
+		}
+
+		return new HashSet<>(allTypes);
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = super.validateFormLogic(ureq);
+		
+		curriculumTreeEl.clearError();
+		if(curriculumTreeEl.getSelectedNode() == null) {
+			curriculumTreeEl.setErrorKey("error.select.target.level", null);
+			allOk &= false;
+		} else if(isParent()) {
+			curriculumTreeEl.setErrorKey("error.target.no.parent", null);
+			allOk &= false;
+		} else if(!targetableNodes.contains(curriculumTreeEl.getSelectedNode())) {
+			curriculumTreeEl.setErrorKey("error.target.not.allowed", null);
+			allOk &= false;
+		}
+
+		return allOk;
+	}
+	
+	private boolean isParent() {
+		boolean parent = false;
+		for(CurriculumElement element:curriculumElementsToMove) {
+			parent |= isParent(element);
+		}
+		return parent;
+	}
+	
+	private boolean isParent(CurriculumElement element) {
+		TreeNode nodeToMove = curriculumModel
+				.getNodeById(CurriculumTreeModel.nodeKey(element));
+		TreeNode selectedNode = curriculumTreeEl.getSelectedNode();
+		if(selectedNode == curriculumModel.getRootNode()) {
+			return false;//can move to root
+		}
+		for(INode node=nodeToMove; node != null; node = node.getParent()) {
+			if(selectedNode == node) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		if(isParent()) {
+			showWarning("error.target.no.parent");
+		} else {
+			TreeNode selectedNode = curriculumTreeEl.getSelectedNode();
+			if(selectedNode == curriculumModel.getRootNode()) {
+				for(CurriculumElement elementToMove:curriculumElementsToMove) {
+					curriculumService.moveCurriculumElement(elementToMove, null);
+				}
+			} else {
+				CurriculumElement newParent = (CurriculumElement)selectedNode.getUserObject();
+				for(CurriculumElement elementToMove:curriculumElementsToMove) {
+					curriculumService.moveCurriculumElement(elementToMove, newParent);
+				}
+			}
+		}
+		fireEvent(ureq, Event.DONE_EVENT);
+	}
+}
diff --git a/src/main/java/org/olat/modules/curriculum/ui/_content/move_curriculum_element.html b/src/main/java/org/olat/modules/curriculum/ui/_content/move_curriculum_element.html
new file mode 100644
index 00000000000..ac76ff73f22
--- /dev/null
+++ b/src/main/java/org/olat/modules/curriculum/ui/_content/move_curriculum_element.html
@@ -0,0 +1,8 @@
+$r.render("elements")
+#if($f.hasError("elements"))
+<div class="">$r.render("elements_ERROR")</div>
+#end
+<div class="o_button_group">
+	$r.render("cancel")
+	$r.render("move.element")
+</div>
\ No newline at end of file
diff --git a/src/main/resources/database/mysql/alter_12_4_x_to_13_0_0.sql b/src/main/resources/database/mysql/alter_12_4_x_to_13_0_0.sql
index 7ab84a52418..f9acb5fc485 100644
--- a/src/main/resources/database/mysql/alter_12_4_x_to_13_0_0.sql
+++ b/src/main/resources/database/mysql/alter_12_4_x_to_13_0_0.sql
@@ -118,6 +118,7 @@ create table o_cur_curriculum_element (
   fk_group bigint not null,
   fk_parent bigint,
   fk_curriculum bigint not null,
+  fk_type bigint,
   primary key (id)
 );
 alter table o_cur_curriculum_element ENGINE = InnoDB;
@@ -125,6 +126,7 @@ alter table o_cur_curriculum_element ENGINE = InnoDB;
 alter table o_cur_curriculum_element add constraint cur_el_to_group_idx foreign key (fk_group) references o_bs_group (id);
 alter table o_cur_curriculum_element add constraint cur_el_to_cur_el_idx foreign key (fk_parent) references o_cur_curriculum_element (id);
 alter table o_cur_curriculum_element add constraint cur_el_to_cur_idx foreign key (fk_curriculum) references o_cur_curriculum (id);
+alter table o_cur_curriculum_element add constraint cur_el_type_to_el_type_idx foreign key (fk_type) references o_cur_element_type (id);
 
 create table o_cur_element_type_to_type (
   id bigint not null auto_increment,
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index 9cd0a890e89..bcdb983e542 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -2474,6 +2474,7 @@
 	  fk_group bigint not null,
 	  fk_parent bigint,
 	  fk_curriculum bigint not null,
+  fk_type bigint,
 	  primary key (id)
 	);
 	
@@ -3430,6 +3431,7 @@
 	alter table o_cur_curriculum_element add constraint cur_el_to_group_idx foreign key (fk_group) references o_bs_group (id);
 	alter table o_cur_curriculum_element add constraint cur_el_to_cur_el_idx foreign key (fk_parent) references o_cur_curriculum_element (id);
 	alter table o_cur_curriculum_element add constraint cur_el_to_cur_idx foreign key (fk_curriculum) references o_cur_curriculum (id);
+	alter table o_cur_curriculum_element add constraint cur_el_type_to_el_type_idx foreign key (fk_type) references o_cur_element_type (id);
 	
 	alter table o_cur_element_type_to_type add constraint cur_type_to_type_idx foreign key (fk_type) references o_cur_element_type (id);
 	alter table o_cur_element_type_to_type add constraint cur_type_to_sub_type_idx foreign key (fk_allowed_sub_type) references o_cur_element_type (id);
diff --git a/src/main/resources/database/oracle/alter_12_4_x_to_13_0_0.sql b/src/main/resources/database/oracle/alter_12_4_x_to_13_0_0.sql
index bb027d260cc..9f3de8b8043 100644
--- a/src/main/resources/database/oracle/alter_12_4_x_to_13_0_0.sql
+++ b/src/main/resources/database/oracle/alter_12_4_x_to_13_0_0.sql
@@ -120,6 +120,7 @@ create table o_cur_curriculum_element (
   fk_group number(20) not null,
   fk_parent number(20),
   fk_curriculum number(20) not null,
+  fk_type number(20),
   primary key (id)
 );
 
@@ -129,6 +130,8 @@ alter table o_cur_curriculum_element add constraint cur_el_to_cur_el_idx foreign
 create index idx_cur_el_to_cur_el_idx on o_cur_curriculum_element (fk_parent);
 alter table o_cur_curriculum_element add constraint cur_el_to_cur_idx foreign key (fk_curriculum) references o_cur_curriculum (id);
 create index idx_cur_el_to_cur_idx on o_cur_curriculum_element (fk_curriculum);
+alter table o_cur_curriculum_element add constraint cur_el_type_to_el_type_idx foreign key (fk_type) references o_cur_element_type (id);
+create index idx_cur_el_type_to_el_type_idx on o_cur_curriculum_element (fk_type);
 
 create table o_cur_element_type_to_type (
   id number(20) generated always as identity,
diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql
index c641dda48cd..52dc3b499f0 100644
--- a/src/main/resources/database/oracle/setupDatabase.sql
+++ b/src/main/resources/database/oracle/setupDatabase.sql
@@ -2518,6 +2518,7 @@ create table o_cur_curriculum_element (
   fk_group number(20) not null,
   fk_parent number(20),
   fk_curriculum number(20) not null,
+  fk_type number(20),
   primary key (id)
 );
 
@@ -3632,6 +3633,8 @@ alter table o_cur_curriculum_element add constraint cur_el_to_cur_el_idx foreign
 create index idx_cur_el_to_cur_el_idx on o_cur_curriculum_element (fk_parent);
 alter table o_cur_curriculum_element add constraint cur_el_to_cur_idx foreign key (fk_curriculum) references o_cur_curriculum (id);
 create index idx_cur_el_to_cur_idx on o_cur_curriculum_element (fk_curriculum);
+alter table o_cur_curriculum_element add constraint cur_el_type_to_el_type_idx foreign key (fk_type) references o_cur_element_type (id);
+create index idx_cur_el_type_to_el_type_idx on o_cur_curriculum_element (fk_type);
 
 alter table o_cur_element_type_to_type add constraint cur_type_to_type_idx foreign key (fk_type) references o_cur_element_type (id);
 create index idx_cur_type_to_type_idx on o_cur_element_type_to_type (fk_type);
diff --git a/src/main/resources/database/postgresql/alter_12_4_x_to_13_0_0.sql b/src/main/resources/database/postgresql/alter_12_4_x_to_13_0_0.sql
index a4c514d8552..61067187a4e 100644
--- a/src/main/resources/database/postgresql/alter_12_4_x_to_13_0_0.sql
+++ b/src/main/resources/database/postgresql/alter_12_4_x_to_13_0_0.sql
@@ -120,6 +120,7 @@ create table o_cur_curriculum_element (
   fk_group int8 not null,
   fk_parent int8,
   fk_curriculum int8 not null,
+  fk_type int8,
   primary key (id)
 );
 
@@ -129,6 +130,9 @@ alter table o_cur_curriculum_element add constraint cur_el_to_cur_el_idx foreign
 create index idx_cur_el_to_cur_el_idx on o_cur_curriculum_element (fk_parent);
 alter table o_cur_curriculum_element add constraint cur_el_to_cur_idx foreign key (fk_curriculum) references o_cur_curriculum (id);
 create index idx_cur_el_to_cur_idx on o_cur_curriculum_element (fk_curriculum);
+alter table o_cur_curriculum_element add constraint cur_el_type_to_el_type_idx foreign key (fk_type) references o_cur_element_type (id);
+create index idx_cur_el_type_to_el_type_idx on o_cur_curriculum_element (fk_type);
+
 
 create table o_cur_element_type_to_type (
   id bigserial,
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index 6c0fac4ccde..2eaaa09fc4a 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -2471,6 +2471,7 @@ create table o_cur_curriculum_element (
   fk_group int8 not null,
   fk_parent int8,
   fk_curriculum int8 not null,
+  fk_type int8,
   primary key (id)
 );
 
@@ -3485,6 +3486,8 @@ alter table o_cur_curriculum_element add constraint cur_el_to_cur_el_idx foreign
 create index idx_cur_el_to_cur_el_idx on o_cur_curriculum_element (fk_parent);
 alter table o_cur_curriculum_element add constraint cur_el_to_cur_idx foreign key (fk_curriculum) references o_cur_curriculum (id);
 create index idx_cur_el_to_cur_idx on o_cur_curriculum_element (fk_curriculum);
+alter table o_cur_curriculum_element add constraint cur_el_type_to_el_type_idx foreign key (fk_type) references o_cur_element_type (id);
+create index idx_cur_el_type_to_el_type_idx on o_cur_curriculum_element (fk_type);
 
 alter table o_cur_element_type_to_type add constraint cur_type_to_type_idx foreign key (fk_type) references o_cur_element_type (id);
 create index idx_cur_type_to_type_idx on o_cur_element_type_to_type (fk_type);
diff --git a/src/test/java/org/olat/modules/curriculum/manager/CurriculumElementTypeDAOTest.java b/src/test/java/org/olat/modules/curriculum/manager/CurriculumElementTypeDAOTest.java
index 6dc5e8247b6..0fa430ab801 100644
--- a/src/test/java/org/olat/modules/curriculum/manager/CurriculumElementTypeDAOTest.java
+++ b/src/test/java/org/olat/modules/curriculum/manager/CurriculumElementTypeDAOTest.java
@@ -83,6 +83,20 @@ public class CurriculumElementTypeDAOTest extends OlatTestCase {
 		Assert.assertEquals("AC-235", reloadedType.getExternalId());
 	}
 	
+	@Test
+	public void loadAlltypes() {
+		CurriculumElementType type = curriculumElementTypeDao.createCurriculumElementType("cur-el-3", "3. Element", "Third element", "AC-236");
+		Assert.assertNotNull(type);
+		dbInstance.commitAndCloseSession();
+		
+		// load the element type
+		List<CurriculumElementType> allTypes = curriculumElementTypeDao.load();
+		dbInstance.commitAndCloseSession();
+		//check
+		Assert.assertNotNull(allTypes);
+		Assert.assertTrue(allTypes.contains(type));
+	}
+	
 	@Test
 	public void allowSubTypes() {
 		CurriculumElementType type = curriculumElementTypeDao.createCurriculumElementType("Type-parent", "A type", null, null);
-- 
GitLab