From d1ba9fd011815e5cafc7e21b1ac61939f12cecdd Mon Sep 17 00:00:00 2001 From: Daniel Haag <daniel.haag@uibk.ac.at> Date: Wed, 20 Jun 2018 14:49:50 +0000 Subject: [PATCH] OPENOLAT-579: major refactoring to use the new CourseFragmentEnvironmentMapper in the node wizard --- .../uibk/course/CreateCourseContentStep.java | 4 +- .../CopyStructureWizardFactory.java | 6 +- .../CopyStructureWizardFactoryImpl.java | 36 +-- ...CopyStructureWizardStepRunnerCallback.java | 66 ++-- .../_i18n/LocalStrings_de.properties | 1 - .../_i18n/LocalStrings_en.properties | 1 - .../AbstractCourseNodeCopyConfigurator.java | 289 +++--------------- .../BCCourseNodeCopyConfigurator.java | 6 +- .../COCourseNodeCopyConfigurator.java | 6 +- .../DialogCourseNodeCopyConfigurator.java | 6 +- .../ENCourseNodeCopyConfigurator.java | 111 +------ .../FOCourseNodeCopyConfigurator.java | 6 +- .../GTACourseNodeCopyConfigurator.java | 6 +- .../ICourseNodeCopyConfigurator.java | 4 +- .../InfoCourseNodeCopyConfigurator.java | 6 +- .../LLCourseNodeCopyConfigurator.java | 6 +- .../PortfolioCourseNodeCopyConfigurator.java | 6 +- ...ojectBrokerCourseNodeCopyConfigurator.java | 9 +- .../SPCourseNodeCopyConfigurator.java | 14 +- .../STCourseNodeCopyConfigurator.java | 45 ++- .../TACourseNodeCopyConfigurator.java | 6 +- .../WikiCourseNodeCopyConfigurator.java | 6 +- .../CourseFragmentEnvironmentMapper.java | 55 ++++ ...StructureWizardStepRunnerCallbackTest.java | 8 +- 24 files changed, 243 insertions(+), 466 deletions(-) create mode 100644 src/main/java/org/olat/course/export/CourseFragmentEnvironmentMapper.java diff --git a/src/main/java/at/ac/uibk/course/CreateCourseContentStep.java b/src/main/java/at/ac/uibk/course/CreateCourseContentStep.java index f575b42460b..3353d999953 100644 --- a/src/main/java/at/ac/uibk/course/CreateCourseContentStep.java +++ b/src/main/java/at/ac/uibk/course/CreateCourseContentStep.java @@ -76,9 +76,7 @@ public class CreateCourseContentStep extends BasicStep { @Override public StepFormController getStepController(UserRequest ureq, WindowControl windowControl, StepsRunContext stepsRunContext, Form form) { stepsRunContext.put("stepProcess", stepProcess); - StepFormController stepP = new CreateCourseContentStepForm(ureq, windowControl, form, stepsRunContext, null); - - return stepP; + return new CreateCourseContentStepForm(ureq, windowControl, form, stepsRunContext, null); } diff --git a/src/main/java/de/bps/course/nodewizard/CopyStructureWizardFactory.java b/src/main/java/de/bps/course/nodewizard/CopyStructureWizardFactory.java index ad64fd082d0..dded773fe70 100644 --- a/src/main/java/de/bps/course/nodewizard/CopyStructureWizardFactory.java +++ b/src/main/java/de/bps/course/nodewizard/CopyStructureWizardFactory.java @@ -26,6 +26,7 @@ import org.olat.core.gui.control.generic.wizard.StepRunnerCallback; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.course.ICourse; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.CourseNode; import org.olat.course.run.environment.CourseEnvironment; @@ -60,7 +61,8 @@ public abstract class CopyStructureWizardFactory { public abstract StepRunnerCallback createCopyStructureWizardStepRunnerCallback(); - public abstract ICourseNodeCopyConfigurator getCourseNodeCopyConfigurator(Identity fallbackIdentity, CourseEnvironment srcCourseEnv, - CourseEnvironment targetCourseEnv, CourseNode srcCourseNode, CourseNode targetCourseNode, boolean canCopyToCourseFolder, Translator translator); + public abstract ICourseNodeCopyConfigurator getCourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, + final CourseEnvironment targetCourseEnv, final CourseNode srcCourseNode, final CourseNode targetCourseNode, + final CourseFragmentEnvironmentMapper courseEnvironmentMapper, final boolean canCopyToCourseFolder); } diff --git a/src/main/java/de/bps/course/nodewizard/CopyStructureWizardFactoryImpl.java b/src/main/java/de/bps/course/nodewizard/CopyStructureWizardFactoryImpl.java index ac47c8cb386..016dd09b49f 100644 --- a/src/main/java/de/bps/course/nodewizard/CopyStructureWizardFactoryImpl.java +++ b/src/main/java/de/bps/course/nodewizard/CopyStructureWizardFactoryImpl.java @@ -22,9 +22,9 @@ package de.bps.course.nodewizard; import org.olat.core.gui.UserRequest; import org.olat.core.gui.control.generic.wizard.Step; import org.olat.core.gui.control.generic.wizard.StepRunnerCallback; -import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.course.ICourse; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.BCCourseNode; import org.olat.course.nodes.COCourseNode; import org.olat.course.nodes.CourseNode; @@ -85,69 +85,69 @@ public class CopyStructureWizardFactoryImpl extends CopyStructureWizardFactory { @Override public ICourseNodeCopyConfigurator getCourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, - final CourseEnvironment targetCourseEnv, final CourseNode srcCourseNode, final CourseNode targetCourseNode, final boolean canCopyToCourseFolder, - final Translator translator) { + final CourseEnvironment targetCourseEnv, final CourseNode srcCourseNode, final CourseNode targetCourseNode, + final CourseFragmentEnvironmentMapper courseEnvironmentMapper, final boolean canCopyToCourseFolder) { ICourseNodeCopyConfigurator configurator; if (srcCourseNode instanceof SPCourseNode) { // Single page configurator = new SPCourseNodeCopyConfigurator(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, - canCopyToCourseFolder, translator); + courseEnvironmentMapper, canCopyToCourseFolder); } else if (srcCourseNode instanceof BCCourseNode) { // Folder configurator = new BCCourseNodeCopyConfigurator(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, - canCopyToCourseFolder, translator); + courseEnvironmentMapper, canCopyToCourseFolder); } else if (srcCourseNode instanceof LLCourseNode) { // link list configurator = new LLCourseNodeCopyConfigurator(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, - canCopyToCourseFolder, translator); + courseEnvironmentMapper, canCopyToCourseFolder); } else if (srcCourseNode instanceof DialogCourseNode) { // dialog configurator = new DialogCourseNodeCopyConfigurator(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, - canCopyToCourseFolder, translator); + courseEnvironmentMapper, canCopyToCourseFolder); } else if (srcCourseNode instanceof FOCourseNode) { // forum configurator = new FOCourseNodeCopyConfigurator(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, - canCopyToCourseFolder, translator); + courseEnvironmentMapper, canCopyToCourseFolder); } else if (srcCourseNode instanceof InfoCourseNode) { // info configurator = new InfoCourseNodeCopyConfigurator(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, - canCopyToCourseFolder, translator); + courseEnvironmentMapper, canCopyToCourseFolder); } else if (srcCourseNode instanceof PortfolioCourseNode) { // portfolio configurator = new PortfolioCourseNodeCopyConfigurator(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, - canCopyToCourseFolder, translator); + courseEnvironmentMapper, canCopyToCourseFolder); } else if (srcCourseNode instanceof ProjectBrokerCourseNode) { // project broker configurator = new ProjectBrokerCourseNodeCopyConfigurator(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, - canCopyToCourseFolder, translator); + courseEnvironmentMapper, canCopyToCourseFolder); } else if (srcCourseNode instanceof TACourseNode) { // task configurator = new TACourseNodeCopyConfigurator(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, - canCopyToCourseFolder, translator); + courseEnvironmentMapper, canCopyToCourseFolder); } else if (srcCourseNode instanceof GTACourseNode) { // task configurator = new GTACourseNodeCopyConfigurator(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, - canCopyToCourseFolder, translator); + courseEnvironmentMapper, canCopyToCourseFolder); } else if (srcCourseNode instanceof WikiCourseNode) { // wiki configurator = new WikiCourseNodeCopyConfigurator(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, - canCopyToCourseFolder, translator); + courseEnvironmentMapper, canCopyToCourseFolder); } else if (srcCourseNode instanceof ENCourseNode) { // enrolment configurator = new ENCourseNodeCopyConfigurator(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, - canCopyToCourseFolder, translator); + courseEnvironmentMapper, canCopyToCourseFolder); } else if (srcCourseNode instanceof COCourseNode) { // mail configurator = new COCourseNodeCopyConfigurator(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, - canCopyToCourseFolder, translator); + courseEnvironmentMapper, canCopyToCourseFolder); } else if (srcCourseNode instanceof STCourseNode) { // structure configurator = new STCourseNodeCopyConfigurator(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, - canCopyToCourseFolder, translator); + courseEnvironmentMapper, canCopyToCourseFolder); } else { configurator = new AbstractCourseNodeCopyConfigurator(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, - canCopyToCourseFolder, translator); + courseEnvironmentMapper, canCopyToCourseFolder); } return configurator; diff --git a/src/main/java/de/bps/course/nodewizard/CopyStructureWizardStepRunnerCallback.java b/src/main/java/de/bps/course/nodewizard/CopyStructureWizardStepRunnerCallback.java index 5202d1e898c..641b0a9bde0 100644 --- a/src/main/java/de/bps/course/nodewizard/CopyStructureWizardStepRunnerCallback.java +++ b/src/main/java/de/bps/course/nodewizard/CopyStructureWizardStepRunnerCallback.java @@ -20,25 +20,28 @@ import org.olat.core.util.nodes.INode; import org.olat.core.util.vfs.MergeSource; import org.olat.core.util.vfs.VFSContainer; import org.olat.course.CourseFactory; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.CourseNode; import org.olat.course.run.environment.CourseEnvironment; +import org.olat.course.tree.CourseEditorTreeModel; import de.bps.course.nodewizard.coursenode.ICourseNodeCopyConfigurator; public class CopyStructureWizardStepRunnerCallback implements StepRunnerCallback { private WizardStepProcess process; + + private CourseFragmentEnvironmentMapper courseFragmentEnvironmentMapper; - private HashMap<String, String> nodeSrcTargetMapping = new HashMap<>(); - - public Map<String, String> getNodeSrcTargetMapping() { - return nodeSrcTargetMapping; + public CourseFragmentEnvironmentMapper getCourseFragmentEnvironmentMapper() { + return courseFragmentEnvironmentMapper; } @Override public Step execute(final UserRequest ureq, final WindowControl wControl, final StepsRunContext runContext) { boolean hasChanges = false; final Translator translator = new PackageTranslator(this.getClass().getPackage().getName(), ureq.getLocale()); + courseFragmentEnvironmentMapper = new CourseFragmentEnvironmentMapper(); process = (WizardStepProcess) runContext.get("stepProcess"); @@ -52,9 +55,11 @@ public class CopyStructureWizardStepRunnerCallback implements StepRunnerCallback if (rootNode.getChildCount() > 0) { hasChanges = true; for (int i = 0; i < rootNode.getChildCount(); i++) { - addChildNode(ureq.getIdentity(), process.getTargetCourse().getRunStructure().getRootNode(), rootNode.getChildAt(i), translator); + addChildNode(ureq.getIdentity(), process.getTargetCourse().getRunStructure().getRootNode(), rootNode.getChildAt(i)); } - nodeSrcTargetMapping.keySet().stream().forEach(srcId -> configureNode(ureq.getIdentity(), srcId, translator)); + // after all the selected nodes are added to the target course, configure all the added nodes and apply the node id mapping + courseFragmentEnvironmentMapper.getNodeSourceIds().stream() + .forEach(srcId -> configureNode(ureq.getIdentity(), srcId, translator)); CourseFactory.saveCourseEditorTreeModel(process.getTargetCourse().getResourceableId()); } return hasChanges ? StepsMainRunController.DONE_MODIFIED : StepsMainRunController.DONE_UNCHANGED; @@ -82,38 +87,30 @@ public class CopyStructureWizardStepRunnerCallback implements StepRunnerCallback } } - private void addChildNode(final Identity fallbackIdentity, CourseNode parentNode, final INode childNode, final Translator translator) { - final CourseNode srcCourseNode = process.getSrcCourse().getRunStructure().getNode(childNode.getIdent()); - CourseNode newSrcCourseNode = srcCourseNode; - if (process.getSelectedKeys().contains(childNode.getIdent())) { + private void addChildNode(final Identity fallbackIdentity, CourseNode targetParentNode, final INode node) { + final CourseNode srcCourseNode = process.getSrcCourse().getRunStructure().getNode(node.getIdent()); + final CourseNode selectedTargetParentNode; + if (process.getSelectedKeys().contains(node.getIdent())) { // add course node - final boolean isNewTitle = process.getTargetCourse().getEditorTreeModel().getNodeById(childNode.getIdent()) != null; - newSrcCourseNode = srcCourseNode.createInstanceForCopy(isNewTitle, process.getTargetCourse(),fallbackIdentity); + CourseEditorTreeModel targetEditorTreeModel = process.getTargetCourse().getEditorTreeModel(); + final boolean isNewTitle = targetEditorTreeModel.getNodeById(node.getIdent()) != null; + final CourseNode targetCourseNode = srcCourseNode.createInstanceForCopy(isNewTitle, process.getTargetCourse(),fallbackIdentity); // Remove the parent and the children references as the new node ends up in the editortreemodel, // where the tree structure is not in the Course Nodes but in the EditorTreeNodes. - newSrcCourseNode.setParent(null); - newSrcCourseNode.removeAllChildren(); + targetCourseNode.setParent(null); + targetCourseNode.removeAllChildren(); //------ - // if parent node not selected then calculate new parent node - if (!process.getSelectedKeys().contains(parentNode.getIdent())) { - INode parent = parentNode; - while (process.getTargetCourse().getEditorTreeModel().getNodeById(parent.getIdent()) == null) { - parent = parent.getParent(); - if (parent.getIdent().equals(process.getSrcCourse().getEditorTreeModel().getRootNode().getIdent())) { - parent = process.getTargetCourse().getEditorTreeModel().getRootNode(); - break; - } - } - parentNode = process.getTargetCourse().getEditorTreeModel().getCourseNode(parent.getIdent()); - } - process.getTargetCourse().getEditorTreeModel().addCourseNode(newSrcCourseNode, parentNode); - nodeSrcTargetMapping.put(childNode.getIdent(), newSrcCourseNode.getIdent()); + targetEditorTreeModel.addCourseNode(targetCourseNode, targetParentNode); + courseFragmentEnvironmentMapper.addNodeIdentKeyPair(srcCourseNode.getIdent(), targetCourseNode.getIdent()); + selectedTargetParentNode = targetCourseNode; + } else { + selectedTargetParentNode = targetParentNode; } // handle children - for (int i = 0; i < childNode.getChildCount(); i++) { - addChildNode(fallbackIdentity, newSrcCourseNode, childNode.getChildAt(i), translator); + for (int i = 0; i < node.getChildCount(); i++) { + addChildNode(fallbackIdentity, selectedTargetParentNode, node.getChildAt(i)); } } @@ -123,16 +120,19 @@ public class CopyStructureWizardStepRunnerCallback implements StepRunnerCallback final CourseEnvironment targetCourseEnv = process.getTargetCourse().getCourseEnvironment(); final CourseNode srcCourseNode = process.getSrcCourse().getRunStructure().getNode(srcNodeId); - final CourseNode targetCourseNode = process.getTargetCourse().getEditorTreeModel().getCourseNode(nodeSrcTargetMapping.get(srcNodeId)); + final CourseNode targetCourseNode = process.getTargetCourse().getEditorTreeModel() + .getCourseNode(courseFragmentEnvironmentMapper.getNodeTargetIdent(srcNodeId)); + // we do not let the individual nodes copy their files if the copy all + // option is selected as all the files should be there already. final boolean canCopyToCourseFolder = !process.getCopyAll(); final ICourseNodeCopyConfigurator cncc = CopyStructureWizardFactory.getInstance().getCourseNodeCopyConfigurator( fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, - targetCourseNode, canCopyToCourseFolder, translator); + targetCourseNode, courseFragmentEnvironmentMapper, canCopyToCourseFolder); cncc.configure(); - cncc.updateNodeReferences(nodeSrcTargetMapping); + cncc.updateNodeReferences(); } diff --git a/src/main/java/de/bps/course/nodewizard/_i18n/LocalStrings_de.properties b/src/main/java/de/bps/course/nodewizard/_i18n/LocalStrings_de.properties index e21bcc62e94..262766eccc8 100644 --- a/src/main/java/de/bps/course/nodewizard/_i18n/LocalStrings_de.properties +++ b/src/main/java/de/bps/course/nodewizard/_i18n/LocalStrings_de.properties @@ -1,4 +1,3 @@ -configurator.sp.copy.extension=Kopie{0} editor.success.info.message=Die gew\u00e4hlten Kursbausteine wurden erfolgreich hinzugef\u00fcgt. editor.tool.entry=Aus anderem Kurs editor.wizard.title=Kursbausteine kopieren diff --git a/src/main/java/de/bps/course/nodewizard/_i18n/LocalStrings_en.properties b/src/main/java/de/bps/course/nodewizard/_i18n/LocalStrings_en.properties index 883b66dd74a..676c23970f3 100644 --- a/src/main/java/de/bps/course/nodewizard/_i18n/LocalStrings_en.properties +++ b/src/main/java/de/bps/course/nodewizard/_i18n/LocalStrings_en.properties @@ -1,4 +1,3 @@ -configurator.sp.copy.extension=Copy{0} editor.success.info.message=Course elements successfully copied into course. editor.tool.entry=From other course editor.wizard.title=Copy course elements from other courses diff --git a/src/main/java/de/bps/course/nodewizard/coursenode/AbstractCourseNodeCopyConfigurator.java b/src/main/java/de/bps/course/nodewizard/coursenode/AbstractCourseNodeCopyConfigurator.java index 34693b8f1b7..2494f5aae17 100644 --- a/src/main/java/de/bps/course/nodewizard/coursenode/AbstractCourseNodeCopyConfigurator.java +++ b/src/main/java/de/bps/course/nodewizard/coursenode/AbstractCourseNodeCopyConfigurator.java @@ -21,26 +21,18 @@ package de.bps.course.nodewizard.coursenode; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.olat.core.CoreSpringFactory; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; -import org.olat.course.CourseFactory; -import org.olat.course.ICourse; import org.olat.course.condition.Condition; +import org.olat.course.condition.KeyAndNameConverter; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.CourseNode; import org.olat.course.run.environment.CourseEnvironment; -import org.olat.group.BusinessGroup; -import org.olat.group.BusinessGroupService; -import org.olat.group.area.BGArea; import org.olat.group.area.BGAreaManager; -import org.olat.repository.RepositoryEntry; -import org.olat.repository.RepositoryManager; -import org.olat.resource.OLATResource; -import org.olat.resource.OLATResourceManager; + /** * @@ -53,13 +45,14 @@ public class AbstractCourseNodeCopyConfigurator implements ICourseNodeCopyConfig protected CourseNode targetCourseNode; protected CourseEnvironment srcCourseEnv; protected CourseEnvironment targetCourseEnv; + protected CourseFragmentEnvironmentMapper courseEnvMapper; protected boolean canCopyToCourseFolder; protected final Identity fallbackIdentity; protected final BGAreaManager areaManager; - protected final Translator translator; public AbstractCourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, final CourseEnvironment targetCourseEnv, - final CourseNode srcCourseNode, final CourseNode targetCourseNode, final boolean canCopyToCourseFolder, final Translator translator) { + final CourseNode srcCourseNode, final CourseNode targetCourseNode, final CourseFragmentEnvironmentMapper courseEnvironmentMapper, + final boolean canCopyToCourseFolder) { // area manager this.areaManager = CoreSpringFactory.getImpl(BGAreaManager.class); this.fallbackIdentity = fallbackIdentity; @@ -67,8 +60,8 @@ public class AbstractCourseNodeCopyConfigurator implements ICourseNodeCopyConfig this.targetCourseNode = targetCourseNode; this.srcCourseEnv = srcCourseEnv; this.targetCourseEnv = targetCourseEnv; + this.courseEnvMapper = courseEnvironmentMapper; this.canCopyToCourseFolder = canCopyToCourseFolder; - this.translator = translator; } @Override @@ -83,14 +76,14 @@ public class AbstractCourseNodeCopyConfigurator implements ICourseNodeCopyConfig // configure conditions configureConditions(srcPreConditionAccess, targetPreConditionAccess); - configureConditions(srcPreConditionVisibility, targetPreConditionVisibility); + configureConditions(srcPreConditionVisibility, targetPreConditionVisibility); } /** * @param srcPreConditionAccess * @param targetPreConditionAccess */ - void configureConditions(final Condition srcPreCondition, final Condition targetPreCondition) { + protected void configureConditions(final Condition srcPreCondition, final Condition targetPreCondition) { // in the abstract implementation configure only general access rules configureOnlyGeneralAccess(srcPreCondition, targetPreCondition); } @@ -102,56 +95,20 @@ public class AbstractCourseNodeCopyConfigurator implements ICourseNodeCopyConfig * @param targetCondition */ private void configureOnlyGeneralAccess(final Condition srcCondition, final Condition targetCondition) { - targetCondition.setExpertMode(false); - targetCondition.clearEasyConfig(); - targetCondition.setEasyModeCoachesAndAdmins(srcCondition.isEasyModeCoachesAndAdmins()); - targetCondition.setEasyModeAlwaysAllowCoachesAndAdmins(srcCondition.isEasyModeAlwaysAllowCoachesAndAdmins()); - targetCondition.setAssessmentMode(srcCondition.isAssessmentMode()); - targetCondition.setConditionExpression(targetCondition.getConditionFromEasyModeConfiguration()); - } - - /** - * @param groupIds - * @return - */ - protected Map<Long, Long> createBusinessGroups(final Map<Long, String> groupIds) { - // business group service - final BusinessGroupService businessGroupService = CoreSpringFactory.getImpl(BusinessGroupService.class); - final Map<Long, Long> oldIdNewIdMap = new HashMap<>(); - // result group ids - final Map<Long, String> mapIdName = new HashMap<>(); - for (final Long id : groupIds.keySet()) { - final BusinessGroup bg = businessGroupService.loadBusinessGroup(id); - if (bg != null) { - final String name = bg.getName(); - mapIdName.put(id, name); - } - } - if (!mapIdName.isEmpty()) { - oldIdNewIdMap.putAll(buildBusinessGroups(mapIdName)); - } - return oldIdNewIdMap; - } - - /** - * @param groupIds - * @return - */ - protected Map<Long, Long> createAreas(final Map<Long, String> groupIds) { - final Map<Long, Long> oldIdNewIdMap = new HashMap<>(); - // result group ids - final Map<Long, String> mapIdName = new HashMap<>(); - for (final Long id : groupIds.keySet()) { - final BGArea bgArea = areaManager.loadArea(id); - if (bgArea != null) { - final String name = bgArea.getName(); - mapIdName.put(id, name); - } - } - if (!mapIdName.isEmpty()) { - oldIdNewIdMap.putAll(buildAreas(mapIdName)); + if (srcCondition.isExpertMode()) { + targetCondition.setConditionExpression(srcCondition.getConditionExpression()); + } else { + // if expert mode is not set, remove easy group and area rules + targetCondition.setEasyModeGroupAccess(null); + targetCondition.setEasyModeGroupAccessIds(null); + targetCondition.setEasyModeGroupAreaAccess(null); + targetCondition.setEasyModeGroupAreaAccessIds(null); + // remove also begin and end dates as they don't make sense anymore in the new course time frame + targetCondition.setEasyModeBeginDate(null); + targetCondition.setEasyModeEndDate(null); + // recalculate the new condition expression + targetCondition.setConditionExpression(targetCondition.getConditionFromEasyModeConfiguration()); } - return oldIdNewIdMap; } protected List<String> listNames(final String list, final String separator) { @@ -164,192 +121,38 @@ public class AbstractCourseNodeCopyConfigurator implements ICourseNodeCopyConfig return names; } - /** - * create business group - * - * @param groupNames - */ - protected void createBusinessGroups(final List<String> groupNames) { - // business group service - final BusinessGroupService businessGroupService = CoreSpringFactory.getImpl(BusinessGroupService.class); - // exists groups in course - boolean groupExists = false; - for (final String groupName : groupNames) { - groupExists = false; - List<BusinessGroup> businessGroups = this.targetCourseEnv.getCourseGroupManager().getAllBusinessGroups(); - for (final BusinessGroup businessGroup : businessGroups) { - if (businessGroup.getName().equals(groupName)) { - // group exists - groupExists = true; - break; - } - } - - // if not exists then create groups - if (!groupExists) { - BusinessGroup srcGroup = null; - businessGroups = this.srcCourseEnv.getCourseGroupManager().getAllBusinessGroups(); - for (final BusinessGroup businessGroup : businessGroups) { - if (businessGroup.getName().equals(groupName)) { - // group exists - groupExists = true; - srcGroup = businessGroup; - break; - } - } - - if (groupExists) { - businessGroupService.copyBusinessGroup(this.fallbackIdentity, srcGroup, srcGroup.getName(), srcGroup.getDescription(), - srcGroup.getMinParticipants(), srcGroup.getMaxParticipants(), true, true, true, false, false, true, false, false); - } - } - } - } - - private Map<Long, Long> buildBusinessGroups(final Map<Long, String> mapIdName) { - final Map<Long, Long> oldIdNewIdMap = new HashMap<>(); - // business group service - final BusinessGroupService businessGroupService = CoreSpringFactory.getImpl(BusinessGroupService.class); - // exists groups in course - boolean groupExists = false; - // new business group - for (final Long oldId : mapIdName.keySet()) { - groupExists = false; - List<BusinessGroup> businessGroups = this.targetCourseEnv.getCourseGroupManager().getAllBusinessGroups(); - for (final BusinessGroup businessGroup : businessGroups) { - if (businessGroup.getName().equals(mapIdName.get(oldId))) { - // group exists - groupExists = true; - oldIdNewIdMap.put(oldId, businessGroup.getKey()); - break; - } - } - - // if not exists then create groups - if (!groupExists) { - BusinessGroup srcGroup = null; - businessGroups = this.srcCourseEnv.getCourseGroupManager().getAllBusinessGroups(); - for (final BusinessGroup businessGroup : businessGroups) { - if (businessGroup.getName().equals(mapIdName.get(oldId))) { - // group exists - groupExists = true; - srcGroup = businessGroup; - break; - } - } - - if (groupExists) { - final BusinessGroup newBusinessGroup = businessGroupService.copyBusinessGroup(this.fallbackIdentity, srcGroup, srcGroup.getName(), - srcGroup.getDescription(), srcGroup.getMinParticipants(), srcGroup.getMaxParticipants(), true, false, true, false, false, true, - false, false); - oldIdNewIdMap.put(oldId, newBusinessGroup.getKey()); - } - } - } - return oldIdNewIdMap; + @Override + public void updateNodeReferences() { + // Override/extend this function if your node references other nodes + updateCondition(targetCourseNode.getPreConditionAccess()); + updateCondition(targetCourseNode.getPreConditionVisibility()); + return; } - - /** - * @param mapIdName - * @return - */ - private Map<Long, Long> buildAreas(final Map<Long, String> mapIdName) { - final Map<Long, Long> oldIdNewIdMap = new HashMap<>(); - // target - final ICourse course = CourseFactory.loadCourse(this.targetCourseEnv.getCourseResourceableId()); - final RepositoryEntry re = RepositoryManager.getInstance().lookupRepositoryEntry(course, false); - // exists areas in course - boolean areaExists = false; - for (final Long oldId : mapIdName.keySet()) { - areaExists = false; - List<BGArea> areas = this.targetCourseEnv.getCourseGroupManager().getAllAreas(); - for (final BGArea area : areas) { - if (area.getName().equals(mapIdName.get(oldId))) { - // group exists - areaExists = true; - oldIdNewIdMap.put(oldId, area.getKey()); - break; - } - } - - // if not exists then create areas - if (!areaExists) { - BGArea srcArea = null; - areas = this.srcCourseEnv.getCourseGroupManager().getAllAreas(); - for (final BGArea area : areas) { - if (area.getName().equals(mapIdName.get(oldId))) { - // group exists - areaExists = true; - srcArea = area; - break; - } - } - - if (areaExists) { - final BGArea newArea = areaManager.createAndPersistBGArea(srcArea.getName(), srcArea.getDescription(), re.getOlatResource()); - oldIdNewIdMap.put(oldId, newArea.getKey()); - } - } + + private void updateCondition(Condition preCondition) { + String easyModeNodePassedId = courseEnvMapper.getNodeTargetIdent(preCondition.getEasyModeNodePassedId()); + if (easyModeNodePassedId != null) { + preCondition.setEasyModeNodePassedId(easyModeNodePassedId); } - return oldIdNewIdMap; - } - - protected void createAreas(final List<String> areaNames) { - // target - final ICourse targetCourse = CourseFactory.loadCourse(this.targetCourseEnv.getCourseResourceableId()); - final OLATResource targetCourseResource = OLATResourceManager.getInstance().findOrPersistResourceable(targetCourse); - // source - final ICourse srcCourse = CourseFactory.loadCourse(this.srcCourseEnv.getCourseResourceableId()); - final OLATResource srcCourseResource = OLATResourceManager.getInstance().findOrPersistResourceable(srcCourse); - - if (targetCourseResource != null && srcCourseResource != null) { - // exists areas in course - boolean areaExists = false; - for (final String areaName : areaNames) { - areaExists = false; - List<BGArea> areas = this.targetCourseEnv.getCourseGroupManager().getAllAreas(); - for (final BGArea area : areas) { - if (area.getName().equals(areaName)) { - // group exists - areaExists = true; - break; - } - } - - // if not exists then create areas - if (!areaExists) { - areas = this.srcCourseEnv.getCourseGroupManager().getAllAreas(); - for (final BGArea area : areas) { - if (area.getName().equals(areaName)) { - // group exists - areaExists = true; - break; - } - } - - if (areaExists) { - areaManager.copyBGAreasOfResource(srcCourseResource, targetCourseResource); - } - } - } + if (preCondition.isExpertMode()) { + preCondition.setConditionExpression( + replaceIdsInCondition(preCondition.getConditionExpression())); + } else { + preCondition.setConditionExpression( + preCondition.getConditionFromEasyModeConfiguration()); } } - @Override - public void updateNodeReferences(Map<String, String> nodeSrcTargetMapping) { - // Override/extend this function if your node references other nodes + protected String replaceIdsInCondition(String condition) { - if (nodeSrcTargetMapping.containsKey(srcCourseNode.getPreConditionAccess().getEasyModeNodePassedId())) { - targetCourseNode.getPreConditionAccess().setEasyModeNodePassedId( - nodeSrcTargetMapping.get(srcCourseNode.getPreConditionAccess().getEasyModeNodePassedId())); - } + condition = KeyAndNameConverter.convertExpressionKeyToKey(condition, courseEnvMapper); - if (nodeSrcTargetMapping.containsKey(srcCourseNode.getPreConditionVisibility().getEasyModeNodePassedId())) { - targetCourseNode.getPreConditionVisibility().setEasyModeNodePassedId( - nodeSrcTargetMapping.get(srcCourseNode.getPreConditionVisibility().getEasyModeNodePassedId())); + for (String nodeSourceId : courseEnvMapper.getNodeSourceIds()) { + condition = condition.replaceAll( + "\"" + nodeSourceId + "\"", + "\"" + courseEnvMapper.getNodeTargetIdent(nodeSourceId) + "\""); } - - return; + return condition; } } diff --git a/src/main/java/de/bps/course/nodewizard/coursenode/BCCourseNodeCopyConfigurator.java b/src/main/java/de/bps/course/nodewizard/coursenode/BCCourseNodeCopyConfigurator.java index 26033843313..1fd17a37fc9 100644 --- a/src/main/java/de/bps/course/nodewizard/coursenode/BCCourseNodeCopyConfigurator.java +++ b/src/main/java/de/bps/course/nodewizard/coursenode/BCCourseNodeCopyConfigurator.java @@ -26,6 +26,7 @@ import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.util.FileUtils; import org.olat.course.condition.Condition; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.BCCourseNode; import org.olat.course.nodes.CourseNode; import org.olat.course.run.environment.CourseEnvironment; @@ -38,8 +39,9 @@ import org.olat.course.run.environment.CourseEnvironment; public class BCCourseNodeCopyConfigurator extends AbstractCourseNodeCopyConfigurator { public BCCourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, final CourseEnvironment targetCourseEnv, - final CourseNode srcCourseNode, final CourseNode targetCourseNode, final boolean canCopyToCourseFolder, final Translator translator) { - super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, canCopyToCourseFolder, translator); + final CourseNode srcCourseNode, final CourseNode targetCourseNode, final CourseFragmentEnvironmentMapper courseEnvironmentMapper, + final boolean canCopyToCourseFolder) { + super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, courseEnvironmentMapper, canCopyToCourseFolder); } @Override diff --git a/src/main/java/de/bps/course/nodewizard/coursenode/COCourseNodeCopyConfigurator.java b/src/main/java/de/bps/course/nodewizard/coursenode/COCourseNodeCopyConfigurator.java index 0b40a7188bf..ccb988f533b 100644 --- a/src/main/java/de/bps/course/nodewizard/coursenode/COCourseNodeCopyConfigurator.java +++ b/src/main/java/de/bps/course/nodewizard/coursenode/COCourseNodeCopyConfigurator.java @@ -19,6 +19,7 @@ package de.bps.course.nodewizard.coursenode; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.CourseNode; import org.olat.course.run.environment.CourseEnvironment; import org.olat.modules.ModuleConfiguration; @@ -41,8 +42,9 @@ public class COCourseNodeCopyConfigurator extends AbstractCourseNodeCopyConfigur * @param translator */ public COCourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, final CourseEnvironment targetCourseEnv, - final CourseNode srcCourseNode, final CourseNode targetCourseNode, final boolean canCopyToCourseFolder, final Translator translator) { - super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, canCopyToCourseFolder, translator); + final CourseNode srcCourseNode, final CourseNode targetCourseNode, final CourseFragmentEnvironmentMapper courseEnvironmentMapper, + final boolean canCopyToCourseFolder) { + super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, courseEnvironmentMapper, canCopyToCourseFolder); } /** diff --git a/src/main/java/de/bps/course/nodewizard/coursenode/DialogCourseNodeCopyConfigurator.java b/src/main/java/de/bps/course/nodewizard/coursenode/DialogCourseNodeCopyConfigurator.java index 3d979fab2d5..9e7b6d7fc2d 100644 --- a/src/main/java/de/bps/course/nodewizard/coursenode/DialogCourseNodeCopyConfigurator.java +++ b/src/main/java/de/bps/course/nodewizard/coursenode/DialogCourseNodeCopyConfigurator.java @@ -22,6 +22,7 @@ package de.bps.course.nodewizard.coursenode; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.course.condition.Condition; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.DialogCourseNode; import org.olat.course.run.environment.CourseEnvironment; @@ -44,8 +45,9 @@ public class DialogCourseNodeCopyConfigurator extends AbstractCourseNodeCopyConf * @param translator */ public DialogCourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, final CourseEnvironment targetCourseEnv, - final CourseNode srcCourseNode, final CourseNode targetCourseNode, final boolean canCopyToCourseFolder, final Translator translator) { - super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, canCopyToCourseFolder, translator); + final CourseNode srcCourseNode, final CourseNode targetCourseNode, final CourseFragmentEnvironmentMapper courseEnvironmentMapper, + final boolean canCopyToCourseFolder) { + super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, courseEnvironmentMapper, canCopyToCourseFolder); } /** diff --git a/src/main/java/de/bps/course/nodewizard/coursenode/ENCourseNodeCopyConfigurator.java b/src/main/java/de/bps/course/nodewizard/coursenode/ENCourseNodeCopyConfigurator.java index e80e3b90c98..ae0da7ad815 100644 --- a/src/main/java/de/bps/course/nodewizard/coursenode/ENCourseNodeCopyConfigurator.java +++ b/src/main/java/de/bps/course/nodewizard/coursenode/ENCourseNodeCopyConfigurator.java @@ -27,11 +27,18 @@ import java.util.Map; import org.olat.core.CoreSpringFactory; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; +import org.olat.core.util.StringHelper; +import org.olat.course.export.CourseEnvironmentMapper; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.ENCourseNode; import org.olat.course.run.environment.CourseEnvironment; import org.olat.group.BusinessGroup; import org.olat.group.BusinessGroupService; +import org.olat.group.area.BGArea; +import org.olat.group.model.BGAreaReference; +import org.olat.group.model.BusinessGroupEnvironment; +import org.olat.group.model.BusinessGroupReference; import org.olat.modules.ModuleConfiguration; /** @@ -52,107 +59,9 @@ public class ENCourseNodeCopyConfigurator extends AbstractCourseNodeCopyConfigur * @param translator */ public ENCourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, final CourseEnvironment targetCourseEnv, - final CourseNode srcCourseNode, final CourseNode targetCourseNode, final boolean canCopyToCourseFolder, final Translator translator) { - super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, canCopyToCourseFolder, translator); - } - - /** - * @see de.bps.course.nodewizard.coursenode.AbstractCourseNodeCopyConfigurator#configure() - */ - @Override - public void configure() { - super.configure(); - - final ModuleConfiguration srcModulConfig = srcCourseNode.getModuleConfiguration(); - final ModuleConfiguration targetModulConfig = targetCourseNode.getModuleConfiguration(); - - // configure group names - configureGroupNames(srcModulConfig); - // configure area names - configureAreaNames(srcModulConfig); - // configure ids - configureIds(srcModulConfig, targetModulConfig, true); - // configure area ids - configureIds(srcModulConfig, targetModulConfig, false); - } - - /** - * @param srcModulConfig - */ - private void configureAreaNames(final ModuleConfiguration srcModulConfig) { - final List<String> areaNames = new ArrayList<String>(); - final String areaName = (String) srcModulConfig.get(ENCourseNode.CONFIG_AREANAME); - areaNames.add(areaName); - createAreas(areaNames); - } - - /** - * @param srcModulConfig - */ - private void configureGroupNames(final ModuleConfiguration srcModulConfig) { - final String groupName = (String) srcModulConfig.get(ENCourseNode.CONFIG_GROUPNAME); - // business group service - final BusinessGroupService businessGroupService = CoreSpringFactory.getImpl(BusinessGroupService.class); - // exists groups in course - boolean groupExists = false; - List<BusinessGroup> businessGroups = this.targetCourseEnv.getCourseGroupManager().getAllBusinessGroups(); - for (final BusinessGroup businessGroup : businessGroups) { - if (businessGroup.getName().equals(groupName)) { - // group exists - groupExists = true; - break; - } - } - if (!groupExists) { - BusinessGroup srcGroup = null; - businessGroups = this.srcCourseEnv.getCourseGroupManager().getAllBusinessGroups(); - for (final BusinessGroup businessGroup : businessGroups) { - if (businessGroup.getName().equals(groupName)) { - // group exists - groupExists = true; - srcGroup = businessGroup; - break; - } - } - - if (groupExists) { - businessGroupService.copyBusinessGroup(this.fallbackIdentity, srcGroup, srcGroup.getName(), srcGroup.getDescription(), - srcGroup.getMinParticipants(), srcGroup.getMaxParticipants(), true, false, true, false, false, true, false, false); - } - } - } - - /** - * @param srcModulConfig - * @param targetModulConfig - * @param isBusinessGroup - */ - private void configureIds(final ModuleConfiguration srcModulConfig, final ModuleConfiguration targetModulConfig, final boolean isBusinessGroup) { - final Map<Long, String> groupIds = new HashMap<Long, String>(); - final Map<Long, Long> oldIdNewIds = new HashMap<Long, Long>(); - if (isBusinessGroup) { - // is business group - @SuppressWarnings("unchecked") - final List<Long> groupKeys = (List<Long>) srcModulConfig.get(ENCourseNode.CONFIG_GROUP_IDS); - if (groupKeys != null) { - for (final Long id : groupKeys) { - groupIds.put(id, ""); - } - } - oldIdNewIds.putAll(createBusinessGroups(groupIds)); - targetModulConfig.set(ENCourseNode.CONFIG_GROUP_IDS, new ArrayList<Long>(oldIdNewIds.values())); - } else { - // is area - @SuppressWarnings("unchecked") - final List<Long> areaKeys = (List<Long>) srcModulConfig.get(ENCourseNode.CONFIG_AREA_IDS); - if (areaKeys != null) { - for (final Long id : areaKeys) { - groupIds.put(id, ""); - } - } - oldIdNewIds.putAll(createAreas(groupIds)); - targetModulConfig.set(ENCourseNode.CONFIG_AREA_IDS, new ArrayList<Long>(oldIdNewIds.values())); - } + final CourseNode srcCourseNode, final CourseNode targetCourseNode, final CourseFragmentEnvironmentMapper courseEnvironmentMapper, + final boolean canCopyToCourseFolder) { + super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, courseEnvironmentMapper, canCopyToCourseFolder); } } diff --git a/src/main/java/de/bps/course/nodewizard/coursenode/FOCourseNodeCopyConfigurator.java b/src/main/java/de/bps/course/nodewizard/coursenode/FOCourseNodeCopyConfigurator.java index 22276d235f2..7a5872a93f5 100644 --- a/src/main/java/de/bps/course/nodewizard/coursenode/FOCourseNodeCopyConfigurator.java +++ b/src/main/java/de/bps/course/nodewizard/coursenode/FOCourseNodeCopyConfigurator.java @@ -22,6 +22,7 @@ package de.bps.course.nodewizard.coursenode; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.course.condition.Condition; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.FOCourseNode; import org.olat.course.run.environment.CourseEnvironment; @@ -44,8 +45,9 @@ public class FOCourseNodeCopyConfigurator extends AbstractCourseNodeCopyConfigur * @param translator */ public FOCourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, final CourseEnvironment targetCourseEnv, - final CourseNode srcCourseNode, final CourseNode targetCourseNode, final boolean canCopyToCourseFolder, final Translator translator) { - super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, canCopyToCourseFolder, translator); + final CourseNode srcCourseNode, final CourseNode targetCourseNode, final CourseFragmentEnvironmentMapper courseEnvironmentMapper, + final boolean canCopyToCourseFolder) { + super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, courseEnvironmentMapper, canCopyToCourseFolder); } /** diff --git a/src/main/java/de/bps/course/nodewizard/coursenode/GTACourseNodeCopyConfigurator.java b/src/main/java/de/bps/course/nodewizard/coursenode/GTACourseNodeCopyConfigurator.java index 152d030a9b5..4ec9f327012 100644 --- a/src/main/java/de/bps/course/nodewizard/coursenode/GTACourseNodeCopyConfigurator.java +++ b/src/main/java/de/bps/course/nodewizard/coursenode/GTACourseNodeCopyConfigurator.java @@ -22,6 +22,7 @@ import org.olat.core.CoreSpringFactory; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.util.FileUtils; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.GTAManager; @@ -46,8 +47,9 @@ public class GTACourseNodeCopyConfigurator extends AbstractCourseNodeCopyConfigu * @param translator */ public GTACourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, final CourseEnvironment targetCourseEnv, - final CourseNode srcCourseNode, final CourseNode targetCourseNode, final boolean canCopyToCourseFolder, final Translator translator) { - super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, canCopyToCourseFolder, translator); + final CourseNode srcCourseNode, final CourseNode targetCourseNode, final CourseFragmentEnvironmentMapper courseEnvironmentMapper, + final boolean canCopyToCourseFolder) { + super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, courseEnvironmentMapper, canCopyToCourseFolder); } @Override diff --git a/src/main/java/de/bps/course/nodewizard/coursenode/ICourseNodeCopyConfigurator.java b/src/main/java/de/bps/course/nodewizard/coursenode/ICourseNodeCopyConfigurator.java index 8a76b096a30..cd07669f741 100644 --- a/src/main/java/de/bps/course/nodewizard/coursenode/ICourseNodeCopyConfigurator.java +++ b/src/main/java/de/bps/course/nodewizard/coursenode/ICourseNodeCopyConfigurator.java @@ -19,8 +19,6 @@ */ package de.bps.course.nodewizard.coursenode; -import java.util.Map; - /** * * @author bja @@ -30,6 +28,6 @@ public interface ICourseNodeCopyConfigurator { void configure(); - void updateNodeReferences(Map<String, String> nodeSrcTargetMapping); + void updateNodeReferences(); } diff --git a/src/main/java/de/bps/course/nodewizard/coursenode/InfoCourseNodeCopyConfigurator.java b/src/main/java/de/bps/course/nodewizard/coursenode/InfoCourseNodeCopyConfigurator.java index 7ea0c426e6f..94efa54f605 100644 --- a/src/main/java/de/bps/course/nodewizard/coursenode/InfoCourseNodeCopyConfigurator.java +++ b/src/main/java/de/bps/course/nodewizard/coursenode/InfoCourseNodeCopyConfigurator.java @@ -22,6 +22,7 @@ package de.bps.course.nodewizard.coursenode; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.course.condition.Condition; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.InfoCourseNode; import org.olat.course.run.environment.CourseEnvironment; @@ -44,8 +45,9 @@ public class InfoCourseNodeCopyConfigurator extends AbstractCourseNodeCopyConfig * @param translator */ public InfoCourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, final CourseEnvironment targetCourseEnv, - final CourseNode srcCourseNode, final CourseNode targetCourseNode, final boolean canCopyToCourseFolder, final Translator translator) { - super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, canCopyToCourseFolder, translator); + final CourseNode srcCourseNode, final CourseNode targetCourseNode, final CourseFragmentEnvironmentMapper courseEnvironmentMapper, + final boolean canCopyToCourseFolder) { + super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, courseEnvironmentMapper, canCopyToCourseFolder); } /** diff --git a/src/main/java/de/bps/course/nodewizard/coursenode/LLCourseNodeCopyConfigurator.java b/src/main/java/de/bps/course/nodewizard/coursenode/LLCourseNodeCopyConfigurator.java index c0abb62e298..9128c54cacf 100644 --- a/src/main/java/de/bps/course/nodewizard/coursenode/LLCourseNodeCopyConfigurator.java +++ b/src/main/java/de/bps/course/nodewizard/coursenode/LLCourseNodeCopyConfigurator.java @@ -23,6 +23,7 @@ import java.util.List; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.CourseNode; import org.olat.course.run.environment.CourseEnvironment; import org.olat.modules.ModuleConfiguration; @@ -45,8 +46,9 @@ public class LLCourseNodeCopyConfigurator extends AbstractCourseNodeCopyConfigur * @param translator */ public LLCourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, final CourseEnvironment targetCourseEnv, - final CourseNode srcCourseNode, final CourseNode targetCourseNode, final boolean canCopyToCourseFolder, final Translator translator) { - super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, canCopyToCourseFolder, translator); + final CourseNode srcCourseNode, final CourseNode targetCourseNode, final CourseFragmentEnvironmentMapper courseEnvironmentMapper, + final boolean canCopyToCourseFolder) { + super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, courseEnvironmentMapper, canCopyToCourseFolder); } @Override diff --git a/src/main/java/de/bps/course/nodewizard/coursenode/PortfolioCourseNodeCopyConfigurator.java b/src/main/java/de/bps/course/nodewizard/coursenode/PortfolioCourseNodeCopyConfigurator.java index d8af08b7d72..78da16a1572 100644 --- a/src/main/java/de/bps/course/nodewizard/coursenode/PortfolioCourseNodeCopyConfigurator.java +++ b/src/main/java/de/bps/course/nodewizard/coursenode/PortfolioCourseNodeCopyConfigurator.java @@ -22,6 +22,7 @@ package de.bps.course.nodewizard.coursenode; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.course.condition.Condition; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.PortfolioCourseNode; import org.olat.course.run.environment.CourseEnvironment; @@ -44,8 +45,9 @@ public class PortfolioCourseNodeCopyConfigurator extends AbstractCourseNodeCopyC * @param translator */ public PortfolioCourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, final CourseEnvironment targetCourseEnv, - final CourseNode srcCourseNode, final CourseNode targetCourseNode, final boolean canCopyToCourseFolder, final Translator translator) { - super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, canCopyToCourseFolder, translator); + final CourseNode srcCourseNode, final CourseNode targetCourseNode, final CourseFragmentEnvironmentMapper courseEnvironmentMapper, + final boolean canCopyToCourseFolder) { + super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, courseEnvironmentMapper, canCopyToCourseFolder); } /** diff --git a/src/main/java/de/bps/course/nodewizard/coursenode/ProjectBrokerCourseNodeCopyConfigurator.java b/src/main/java/de/bps/course/nodewizard/coursenode/ProjectBrokerCourseNodeCopyConfigurator.java index bceb2834ea5..2eea68cf728 100644 --- a/src/main/java/de/bps/course/nodewizard/coursenode/ProjectBrokerCourseNodeCopyConfigurator.java +++ b/src/main/java/de/bps/course/nodewizard/coursenode/ProjectBrokerCourseNodeCopyConfigurator.java @@ -22,6 +22,7 @@ package de.bps.course.nodewizard.coursenode; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.course.condition.Condition; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.ProjectBrokerCourseNode; import org.olat.course.run.environment.CourseEnvironment; @@ -43,10 +44,10 @@ public class ProjectBrokerCourseNodeCopyConfigurator extends AbstractCourseNodeC * @param canCopyToCourseFolder * @param translator */ - public ProjectBrokerCourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, - final CourseEnvironment targetCourseEnv, final CourseNode srcCourseNode, final CourseNode targetCourseNode, final boolean canCopyToCourseFolder, - final Translator translator) { - super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, canCopyToCourseFolder, translator); + public ProjectBrokerCourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, final CourseEnvironment targetCourseEnv, + final CourseNode srcCourseNode, final CourseNode targetCourseNode, final CourseFragmentEnvironmentMapper courseEnvironmentMapper, + final boolean canCopyToCourseFolder) { + super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, courseEnvironmentMapper, canCopyToCourseFolder); } /** diff --git a/src/main/java/de/bps/course/nodewizard/coursenode/SPCourseNodeCopyConfigurator.java b/src/main/java/de/bps/course/nodewizard/coursenode/SPCourseNodeCopyConfigurator.java index 08fcc226483..27fef783a64 100644 --- a/src/main/java/de/bps/course/nodewizard/coursenode/SPCourseNodeCopyConfigurator.java +++ b/src/main/java/de/bps/course/nodewizard/coursenode/SPCourseNodeCopyConfigurator.java @@ -26,11 +26,13 @@ import java.nio.file.Paths; import java.util.regex.Pattern; import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; +import org.olat.core.gui.translator.PackageTranslator; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.util.FileUtils; import org.olat.core.util.vfs.MergeSource; import org.olat.core.util.vfs.VFSContainer; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.sp.SPEditController; import org.olat.course.run.environment.CourseEnvironment; @@ -42,11 +44,12 @@ import org.olat.course.run.environment.CourseEnvironment; */ public class SPCourseNodeCopyConfigurator extends AbstractCourseNodeCopyConfigurator { - public static final Integer FILE_RENAME_LIMIT = 20; + public static final Integer FILE_RENAME_LIMIT = 100; public SPCourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, final CourseEnvironment targetCourseEnv, - final CourseNode srcCourseNode, final CourseNode targetCourseNode, final boolean canCopyToCourseFolder, final Translator translator) { - super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, canCopyToCourseFolder, translator); + final CourseNode srcCourseNode, final CourseNode targetCourseNode, final CourseFragmentEnvironmentMapper courseEnvironmentMapper, + final boolean canCopyToCourseFolder) { + super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, courseEnvironmentMapper, canCopyToCourseFolder); } public static Path enhanceWithCopyPattern(final Path filePath, final String copyExtension ) { @@ -63,10 +66,9 @@ public class SPCourseNodeCopyConfigurator extends AbstractCourseNodeCopyConfigur private File enhancedFilenameIfExists(File origFile) { File newFile = origFile; - Integer i = 0; + Integer i = 1; while (newFile.exists() && i < FILE_RENAME_LIMIT) { - newFile = enhanceWithCopyPattern(origFile.toPath(), translator.translate("configurator.sp.copy.extension", - new String[] {i > 0 ? i.toString() : ""})).toFile(); + newFile = enhanceWithCopyPattern(origFile.toPath(), i.toString()).toFile(); i++; } return newFile; diff --git a/src/main/java/de/bps/course/nodewizard/coursenode/STCourseNodeCopyConfigurator.java b/src/main/java/de/bps/course/nodewizard/coursenode/STCourseNodeCopyConfigurator.java index a796392b844..90573d26271 100644 --- a/src/main/java/de/bps/course/nodewizard/coursenode/STCourseNodeCopyConfigurator.java +++ b/src/main/java/de/bps/course/nodewizard/coursenode/STCourseNodeCopyConfigurator.java @@ -20,6 +20,7 @@ package de.bps.course.nodewizard.coursenode; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import org.olat.core.gui.translator.Translator; @@ -27,6 +28,7 @@ import org.olat.core.id.Identity; import org.olat.core.id.IdentityEnvironment; import org.olat.core.id.Roles; import org.olat.course.condition.interpreter.ConditionInterpreter; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.STCourseNode; import org.olat.course.run.environment.CourseEnvironment; @@ -41,8 +43,6 @@ import org.olat.course.run.userview.UserCourseEnvironmentImpl; */ public class STCourseNodeCopyConfigurator extends AbstractCourseNodeCopyConfigurator { - private ConditionInterpreter conditionInterpreter; - /** * @param fallbackIdentity * @param srcCourseEnv @@ -53,25 +53,14 @@ public class STCourseNodeCopyConfigurator extends AbstractCourseNodeCopyConfigur * @param translator */ public STCourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, final CourseEnvironment targetCourseEnv, - final CourseNode srcCourseNode, final CourseNode targetCourseNode, final boolean canCopyToCourseFolder, final Translator translator) { - super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, canCopyToCourseFolder, translator); - - Roles roles = new Roles(false, false, false, false, false, false, false); - IdentityEnvironment identityEnv = new IdentityEnvironment(fallbackIdentity, roles); - UserCourseEnvironment uce = new UserCourseEnvironmentImpl(identityEnv, targetCourseEnv); - conditionInterpreter = new ConditionInterpreter(uce); - } - - private String replaceIdsInCondition(String condition, Map<String, String> mapping) { - for (Map.Entry<String, String> entry : mapping.entrySet()) { - condition = condition.replaceAll("\"" + entry.getKey() + "\"", "\"" + entry.getValue() + "\""); - } - return condition; + final CourseNode srcCourseNode, final CourseNode targetCourseNode, final CourseFragmentEnvironmentMapper courseEnvironmentMapper, + final boolean canCopyToCourseFolder) { + super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, courseEnvironmentMapper, canCopyToCourseFolder); } @Override - public void updateNodeReferences(Map<String, String> nodeSrcTargetMapping) { - super.updateNodeReferences(nodeSrcTargetMapping); + public void updateNodeReferences() { + super.updateNodeReferences(); STCourseNode srcNode = (STCourseNode) srcCourseNode; STCourseNode targetNode = (STCourseNode) targetCourseNode; @@ -79,8 +68,9 @@ public class STCourseNodeCopyConfigurator extends AbstractCourseNodeCopyConfigur List<String> snodes = srcNode.getScoreCalculator().getSumOfScoreNodes(); if (snodes != null) { targetNode.getScoreCalculator().setSumOfScoreNodes( - snodes.stream().filter(nodeSrcTargetMapping::containsKey) - .map(nodeSrcTargetMapping::get) + snodes.stream() + .map(courseEnvMapper::getNodeTargetIdent) + .filter(Objects::nonNull) .collect(Collectors.toList())); if (!targetNode.getScoreCalculator().isExpertMode()) { targetNode.getScoreCalculator().setScoreExpression( @@ -91,8 +81,9 @@ public class STCourseNodeCopyConfigurator extends AbstractCourseNodeCopyConfigur List<String> spnodes = srcNode.getScoreCalculator().getPassedNodes(); if (spnodes != null) { targetNode.getScoreCalculator().setPassedNodes( - spnodes.stream().filter(nodeSrcTargetMapping::containsKey) - .map(nodeSrcTargetMapping::get) + spnodes.stream() + .map(courseEnvMapper::getNodeTargetIdent) + .filter(Objects::nonNull) .collect(Collectors.toList())); if (!targetNode.getScoreCalculator().isExpertMode()) { targetNode.getScoreCalculator().setPassedExpression( @@ -101,13 +92,11 @@ public class STCourseNodeCopyConfigurator extends AbstractCourseNodeCopyConfigur } if (targetNode.getScoreCalculator().isExpertMode()) { - String passedExpression = replaceIdsInCondition(targetNode.getScoreCalculator().getPassedExpression(), nodeSrcTargetMapping); - conditionInterpreter.evaluateCondition(passedExpression); - targetNode.getScoreCalculator().setPassedExpression(passedExpression); + targetNode.getScoreCalculator().setPassedExpression( + replaceIdsInCondition(targetNode.getScoreCalculator().getPassedExpression())); - String scoreExpression = replaceIdsInCondition(targetNode.getScoreCalculator().getScoreExpression(), nodeSrcTargetMapping); - conditionInterpreter.evaluateCalculation(scoreExpression); - targetNode.getScoreCalculator().setScoreExpression(scoreExpression); + targetNode.getScoreCalculator().setScoreExpression( + replaceIdsInCondition(targetNode.getScoreCalculator().getScoreExpression())); } return; diff --git a/src/main/java/de/bps/course/nodewizard/coursenode/TACourseNodeCopyConfigurator.java b/src/main/java/de/bps/course/nodewizard/coursenode/TACourseNodeCopyConfigurator.java index 3f3afb2f09c..e682abc48c5 100644 --- a/src/main/java/de/bps/course/nodewizard/coursenode/TACourseNodeCopyConfigurator.java +++ b/src/main/java/de/bps/course/nodewizard/coursenode/TACourseNodeCopyConfigurator.java @@ -26,6 +26,7 @@ import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.util.FileUtils; import org.olat.course.condition.Condition; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.TACourseNode; import org.olat.course.run.environment.CourseEnvironment; @@ -48,8 +49,9 @@ public class TACourseNodeCopyConfigurator extends AbstractCourseNodeCopyConfigur * @param translator */ public TACourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, final CourseEnvironment targetCourseEnv, - final CourseNode srcCourseNode, final CourseNode targetCourseNode, final boolean canCopyToCourseFolder, final Translator translator) { - super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, canCopyToCourseFolder, translator); + final CourseNode srcCourseNode, final CourseNode targetCourseNode, final CourseFragmentEnvironmentMapper courseEnvironmentMapper, + final boolean canCopyToCourseFolder) { + super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, courseEnvironmentMapper, canCopyToCourseFolder); } @Override diff --git a/src/main/java/de/bps/course/nodewizard/coursenode/WikiCourseNodeCopyConfigurator.java b/src/main/java/de/bps/course/nodewizard/coursenode/WikiCourseNodeCopyConfigurator.java index 75615b64a39..9b090d7441e 100644 --- a/src/main/java/de/bps/course/nodewizard/coursenode/WikiCourseNodeCopyConfigurator.java +++ b/src/main/java/de/bps/course/nodewizard/coursenode/WikiCourseNodeCopyConfigurator.java @@ -22,6 +22,7 @@ package de.bps.course.nodewizard.coursenode; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.course.condition.Condition; +import org.olat.course.export.CourseFragmentEnvironmentMapper; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.WikiCourseNode; import org.olat.course.run.environment.CourseEnvironment; @@ -44,8 +45,9 @@ public class WikiCourseNodeCopyConfigurator extends AbstractCourseNodeCopyConfig * @param translator */ public WikiCourseNodeCopyConfigurator(final Identity fallbackIdentity, final CourseEnvironment srcCourseEnv, final CourseEnvironment targetCourseEnv, - final CourseNode srcCourseNode, final CourseNode targetCourseNode, final boolean canCopyToCourseFolder, final Translator translator) { - super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, canCopyToCourseFolder, translator); + final CourseNode srcCourseNode, final CourseNode targetCourseNode, final CourseFragmentEnvironmentMapper courseEnvironmentMapper, + final boolean canCopyToCourseFolder) { + super(fallbackIdentity, srcCourseEnv, targetCourseEnv, srcCourseNode, targetCourseNode, courseEnvironmentMapper, canCopyToCourseFolder); } /** diff --git a/src/main/java/org/olat/course/export/CourseFragmentEnvironmentMapper.java b/src/main/java/org/olat/course/export/CourseFragmentEnvironmentMapper.java new file mode 100644 index 00000000000..93b5aa60afc --- /dev/null +++ b/src/main/java/org/olat/course/export/CourseFragmentEnvironmentMapper.java @@ -0,0 +1,55 @@ +/** + * <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> + * Original Author: + * Daniel Haag <at> uibk.ac.at + * <p> + */ +package org.olat.course.export; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.olat.course.export.CourseEnvironmentMapper; + +/** + * This subclass of CourseEnvironmentMapper is used when only a node subset is mapped + * resulting in new node identifiers for the nodes as they might be imported into a new + * course multiple times. + * + * @author Daniel Haag + */ +public class CourseFragmentEnvironmentMapper extends CourseEnvironmentMapper { + + private Map<String,String> courseNodeIdentMap; + + public CourseFragmentEnvironmentMapper() { + super(); + courseNodeIdentMap = new HashMap<>(); + } + + public void addNodeIdentKeyPair(String sourceIdent, String targetIdent) { + courseNodeIdentMap.put(sourceIdent, targetIdent); + } + + public String getNodeTargetIdent(String sourceIdent) { + return courseNodeIdentMap.get(sourceIdent); + } + + public Set<String> getNodeSourceIds() { + return courseNodeIdentMap.keySet(); + } +} diff --git a/src/test/java/de/bps/course/nodewizard/CopyStructureWizardStepRunnerCallbackTest.java b/src/test/java/de/bps/course/nodewizard/CopyStructureWizardStepRunnerCallbackTest.java index c29a1a22a24..dca333c4e7d 100644 --- a/src/test/java/de/bps/course/nodewizard/CopyStructureWizardStepRunnerCallbackTest.java +++ b/src/test/java/de/bps/course/nodewizard/CopyStructureWizardStepRunnerCallbackTest.java @@ -98,13 +98,15 @@ public class CopyStructureWizardStepRunnerCallbackTest extends OlatTestCase { List<INode> newTargetNodes = getFlatNodeListFromCourseNode(newTargetCourse.getRunStructure().getRootNode()); selectedNodeIds.stream().forEach( - n -> assertTrue("Node ID " + n + " is present in mapping", stepRunnerCallback.getNodeSrcTargetMapping().containsKey(n)) + n -> assertTrue("Node ID " + n + " is present in mapping", stepRunnerCallback.getCourseFragmentEnvironmentMapper() + .getNodeSourceIds().contains(n)) ); sourceNodes.stream().filter(n -> selectedNodeIds.contains(n.getIdent())).forEach( - x -> assertTrue("Node " + x.toString() + " is present in target course with ID " + stepRunnerCallback.getNodeSrcTargetMapping().get(x.getIdent()), + x -> assertTrue("Node " + x.toString() + " is present in target course with ID " + + stepRunnerCallback.getCourseFragmentEnvironmentMapper().getNodeTargetIdent(x.getIdent()), newTargetNodes.stream().anyMatch( - tn -> tn.getIdent().equals(stepRunnerCallback.getNodeSrcTargetMapping().get(x.getIdent())) + tn -> tn.getIdent().equals(stepRunnerCallback.getCourseFragmentEnvironmentMapper().getNodeTargetIdent(x.getIdent())) ) ) ); -- GitLab