Index: lams_central/src/java/org/lamsfoundation/lams/web/planner/PedagogicalPlannerAction.java =================================================================== diff -u -re2e2aa63f31be07287637a2bec159d1574c0ba2c -r2c9c32f3bdd5d45bbbf1963a071a09aa09724927 --- lams_central/src/java/org/lamsfoundation/lams/web/planner/PedagogicalPlannerAction.java (.../PedagogicalPlannerAction.java) (revision e2e2aa63f31be07287637a2bec159d1574c0ba2c) +++ lams_central/src/java/org/lamsfoundation/lams/web/planner/PedagogicalPlannerAction.java (.../PedagogicalPlannerAction.java) (revision 2c9c32f3bdd5d45bbbf1963a071a09aa09724927) @@ -75,8 +75,6 @@ import org.apache.struts.upload.FormFile; import org.lamsfoundation.lams.authoring.service.IAuthoringService; import org.lamsfoundation.lams.authoring.web.AuthoringConstants; -import org.lamsfoundation.lams.contentrepository.FileException; -import org.lamsfoundation.lams.contentrepository.ItemNotFoundException; import org.lamsfoundation.lams.contentrepository.RepositoryCheckedException; import org.lamsfoundation.lams.contentrepository.client.ToolContentHandler; import org.lamsfoundation.lams.learningdesign.Activity; @@ -192,6 +190,7 @@ private static final String ERROR_KEY_IMPORT = "error.planner.import"; private static final String ERROR_KEY_FILTER_PARSE = "error.planner.filter.parse"; private static final String ERROR_KEY_EXPORT_TEMPLATE = "error.planner.export.template"; + private static final String ERROR_KEY_REMOVE_NODE_TREE = "error.planner.remove.node.tree"; // Error messages used in this class. They are ment to be thrown. private static final String ERROR_USER_NOT_FOUND = "User not found."; @@ -320,7 +319,7 @@ PedagogicalPlannerSequenceNode node = null; if (learningDesignId == null && nodeUid != null) { // we are opening a LD from a certain node, so check is we are allowed to do it - isEditor = isEditor(request, nodeUid); + isEditor = canEditNode(request, nodeUid); node = getPedagogicalPlannerDAO().getByUid(nodeUid); learningDesignId = node.getLearningDesignId(); } @@ -431,7 +430,7 @@ if (nodeUid != null) { PedagogicalPlannerSequenceNode node = getPedagogicalPlannerDAO().getByUid(nodeUid); if (node != null) { - boolean isEditor = isEditor(request, nodeUid); + boolean isEditor = canEditNode(request, nodeUid); Integer nodePermissions = node.getPermissions(); planner.setPermissions(nodePermissions, isEditor); @@ -778,11 +777,10 @@ // Only certain roles can open the editor boolean isSysAdmin = request.isUserInRole(Role.SYSADMIN); - boolean hasRole = isSysAdmin || isEditor(request, nodeUid); + boolean canEdit = canEditNode(request, nodeUid); Boolean edit = WebUtil.readBooleanParam(request, CentralConstants.PARAM_EDIT, false); - edit &= hasRole; + edit &= canEdit; - // Fill the DTO PedagogicalPlannerSequenceNodeDTO dto = null; if (filterText != null) { @@ -831,7 +829,7 @@ Boolean importNode = WebUtil.readBooleanParam(request, CentralConstants.PARAM_IMPORT_NODE, false); dto.setCreateSubnode(createSubnode); dto.setEdit(edit); - dto.setIsEditor(hasRole); + dto.setIsEditor(canEdit); dto.setImportNode(importNode); dto.setTitlePath(titlePath); @@ -885,10 +883,11 @@ PedagogicalPlannerSequenceNodeForm nodeForm = (PedagogicalPlannerSequenceNodeForm) form; boolean newRootNode = false; + Long parentUid = null; if (nodeUid == null) { // It's a new subnode node = new PedagogicalPlannerSequenceNode(); - Long parentUid = nodeForm.getParentUid() == 0 ? null : nodeForm.getParentUid(); + parentUid = nodeForm.getParentUid() == 0 ? null : nodeForm.getParentUid(); if (parentUid != null) { PedagogicalPlannerSequenceNode parent = getPedagogicalPlannerDAO().getByUid(parentUid); node.setParent(parent); @@ -902,8 +901,11 @@ node = getPedagogicalPlannerDAO().getByUid(nodeUid); nodeUid = node.getUid(); } - - if (!isEditor(request, nodeUid)) { + + // either user can edit current node or he is creating a subnode, + // so he needs permissions for the parent node + if (nodeUid == null ? (parentUid == null ? !canEditNode(request, nodeUid) : !canEditNode(request, parentUid)) + : !canEditNode(request, nodeUid)) { log.debug("Unauthorised attempt to saveSequenceNode"); throw new UserAccessDeniedException(); } @@ -934,6 +936,7 @@ */ node.setLearningDesignId(null); node.setLearningDesignTitle(null); + node.setPermissions(PedagogicalPlannerSequenceNode.PERMISSION_DEFAULT_SETTING); } else if (nodeForm.getFile() != null && nodeForm.getFile().getFileSize() > 0) { // file has been uploaded, so copy it to file system and import LD FormFile file = nodeForm.getFile(); @@ -1082,13 +1085,16 @@ Long nodeUid = WebUtil.readLongParam(request, CentralConstants.PARAM_UID); PedagogicalPlannerSequenceNode node = getPedagogicalPlannerDAO().getByUid(nodeUid); Long parentUid = node.getParent() == null ? null : node.getParent().getUid(); - - if (isEditor(request, nodeUid)) { + if (canRemoveSubtree(request, nodeUid)) { PedagogicalPlannerAction.log.debug("Removing sequence node with UID" + nodeUid); getPedagogicalPlannerDAO().removeNode(node); + } else { + ActionMessages errors = new ActionMessages(); + errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage( + PedagogicalPlannerAction.ERROR_KEY_REMOVE_NODE_TREE)); + saveErrors(request, errors); log.debug("Unauthorised attempt to removeSequenceNode"); - throw new UserAccessDeniedException(); } return openSequenceNode(mapping, form, request, parentUid); } @@ -1246,7 +1252,7 @@ public ActionForward importNode(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws ServletException { - if (!isEditor(request, null)) { + if (!canEditNode(request, null)) { log.debug("Unauthorised access to importNode"); throw new UserAccessDeniedException(); } @@ -1886,7 +1892,7 @@ HttpServletResponse response) throws Exception { Long nodeUid = WebUtil.readLongParam(request, CentralConstants.PARAM_UID, true); - if (isEditor(request, nodeUid)) { + if (canEditNode(request, nodeUid)) { List existingUsers = getPedagogicalPlannerDAO().getNodeUsers(nodeUid, Role.ROLE_AUTHOR_ADMIN); Integer orgId = getUserManagementService().getRootOrganisation().getOrganisationId(); @@ -1935,7 +1941,7 @@ Integer userId = WebUtil.readIntParam(request, CentralConstants.PARAM_USER_ID, false); Long nodeUid = WebUtil.readLongParam(request, CentralConstants.PARAM_UID, true); - if (isEditor(request, nodeUid)) { + if (canEditNode(request, nodeUid)) { getPedagogicalPlannerDAO().saveNodeRole(userId, nodeUid, Role.ROLE_AUTHOR_ADMIN); } else { log.debug("Unauthorised attempt to add editor to node."); @@ -1949,7 +1955,7 @@ Integer userId = WebUtil.readIntParam(request, CentralConstants.PARAM_USER_ID, false); Long nodeUid = WebUtil.readLongParam(request, CentralConstants.PARAM_UID, false); - if (isEditor(request, nodeUid)) { + if (canEditNode(request, nodeUid)) { getPedagogicalPlannerDAO().removeNodeRole(userId, nodeUid, Role.ROLE_AUTHOR_ADMIN); } else { log.debug("Unauthorised attempt to remove editor from node."); @@ -1961,7 +1967,11 @@ /*------------------------ COMMON METHODS --------------------*/ // only these roles can edit nodes and give this role on this node to others - private Boolean isEditor(HttpServletRequest request, Long nodeUid) { + private boolean canEditNode(HttpServletRequest request, Long nodeUid) { + return isNodeOwnerOrSuperuser(request, nodeUid) || isEditor(request, nodeUid); + } + + private boolean isNodeOwnerOrSuperuser(HttpServletRequest request, Long nodeUid) { if (request.isUserInRole(Role.SYSADMIN)) { // sysadmins have all permission return true; @@ -1970,17 +1980,46 @@ // all global author admins (GAA) can create and edit at the root level return getUserManagementService().isUserGlobalAuthorAdmin(); } else { - // at any other node, a GAA needs to be linked to that node or one of its parents - return isNodeEditor(request, nodeUid); + // at any other node, the user needs to be the node's owner + // or linked to that node or one of its parents + User user = (User) getUserManagementService().getUserByLogin(request.getRemoteUser()); + PedagogicalPlannerSequenceNode node = getPedagogicalPlannerDAO().getByUid(nodeUid); + User nodeOwner = node.getUser(); + return nodeOwner != null && user.equals(nodeOwner); } } } - private Boolean isNodeEditor(HttpServletRequest request, Long nodeUid) { + private boolean isEditor(HttpServletRequest request, Long nodeUid) { User user = (User) getUserManagementService().getUserByLogin(request.getRemoteUser()); return getPedagogicalPlannerDAO().isEditor(user.getUserId(), nodeUid, Role.ROLE_AUTHOR_ADMIN); } - + + private boolean canRemoveSubtree(HttpServletRequest request, Long nodeUid) { + if (nodeUid == null || request.isUserInRole(Role.SYSADMIN)) { + return true; + } + boolean isOwner = isNodeOwnerOrSuperuser(request, nodeUid); + boolean isPlainEditor = isEditor(request, nodeUid); + if (isOwner || isPlainEditor) { + PedagogicalPlannerSequenceNode node = getPedagogicalPlannerDAO().getByUid(nodeUid); + Integer nodePermissions = node.getPermissions(); + if (isOwner || nodePermissions == null + || (nodePermissions & PedagogicalPlannerSequenceNode.PERMISSION_EDITOR_REMOVE) != 0) { + Set subnodes = node.getSubnodes(); + if (subnodes != null && !subnodes.isEmpty()) { + for (PedagogicalPlannerSequenceNode subnode : subnodes) { + if (!canRemoveSubtree(request, subnode.getUid())) { + return false; + } + } + } + return true; + } + } + return false; + } + private IExportToolContentService getExportService() { if (PedagogicalPlannerAction.exportService == null) { WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(getServlet() @@ -2088,5 +2127,4 @@ } } } - } \ No newline at end of file Index: lams_central/src/java/org/lamsfoundation/lams/web/planner/PedagogicalPlannerSequenceNodeForm.java =================================================================== diff -u -re2e2aa63f31be07287637a2bec159d1574c0ba2c -r2c9c32f3bdd5d45bbbf1963a071a09aa09724927 --- lams_central/src/java/org/lamsfoundation/lams/web/planner/PedagogicalPlannerSequenceNodeForm.java (.../PedagogicalPlannerSequenceNodeForm.java) (revision e2e2aa63f31be07287637a2bec159d1574c0ba2c) +++ lams_central/src/java/org/lamsfoundation/lams/web/planner/PedagogicalPlannerSequenceNodeForm.java (.../PedagogicalPlannerSequenceNodeForm.java) (revision 2c9c32f3bdd5d45bbbf1963a071a09aa09724927) @@ -44,7 +44,7 @@ private Boolean permitEditorViewTemplate; private Boolean permitEditorModifyTemplate; private Boolean permitEditorReplaceTemplate; - private Boolean permitEditorRemoveTemplate; + private Boolean permitEditorRemoveNode; private Boolean permitTeacherViewTemplate; private Boolean permitTeacherEditCopy; @@ -64,7 +64,7 @@ || (nodePermissions & PedagogicalPlannerSequenceNode.PERMISSION_EDITOR_MODIFY) != 0; permitEditorReplaceTemplate = nodePermissions != null && (nodePermissions & PedagogicalPlannerSequenceNode.PERMISSION_EDITOR_REPLACE) != 0; - permitEditorRemoveTemplate = nodePermissions != null + permitEditorRemoveNode = nodePermissions != null && (nodePermissions & PedagogicalPlannerSequenceNode.PERMISSION_EDITOR_REMOVE) != 0; permitTeacherViewTemplate = nodePermissions == null @@ -92,7 +92,7 @@ : 0; permissions += Boolean.TRUE.equals(permitEditorReplaceTemplate) ? PedagogicalPlannerSequenceNode.PERMISSION_EDITOR_REPLACE : 0; - permissions += Boolean.TRUE.equals(permitEditorRemoveTemplate) ? PedagogicalPlannerSequenceNode.PERMISSION_EDITOR_REMOVE + permissions += Boolean.TRUE.equals(permitEditorRemoveNode) ? PedagogicalPlannerSequenceNode.PERMISSION_EDITOR_REMOVE : 0; permissions += Boolean.TRUE.equals(permitTeacherViewTemplate) ? PedagogicalPlannerSequenceNode.PERMISSION_TEACHER_VIEW : 0; @@ -205,12 +205,12 @@ this.permitEditorReplaceTemplate = permitEditorReplaceTemplate; } - public Boolean getPermitEditorRemoveTemplate() { - return permitEditorRemoveTemplate; + public Boolean getPermitEditorRemoveNode() { + return permitEditorRemoveNode; } - public void setPermitEditorRemoveTemplate(Boolean permitEditorRemoveTemplate) { - this.permitEditorRemoveTemplate = permitEditorRemoveTemplate; + public void setPermitEditorRemoveNode(Boolean permitEditorRemoveTemplate) { + this.permitEditorRemoveNode = permitEditorRemoveTemplate; } public Boolean getPermitTeacherViewTemplate() { Index: lams_central/web/includes/javascript/pedagogicalPlanner.js =================================================================== diff -u -re2e2aa63f31be07287637a2bec159d1574c0ba2c -r2c9c32f3bdd5d45bbbf1963a071a09aa09724927 --- lams_central/web/includes/javascript/pedagogicalPlanner.js (.../pedagogicalPlanner.js) (revision e2e2aa63f31be07287637a2bec159d1574c0ba2c) +++ lams_central/web/includes/javascript/pedagogicalPlanner.js (.../pedagogicalPlanner.js) (revision 2c9c32f3bdd5d45bbbf1963a071a09aa09724927) @@ -240,15 +240,15 @@ if ($('#permitEditorViewTemplate').attr('checked') == false) { $('#permitEditorModifyTemplate').attr('checked', false); $('#permitEditorReplaceTemplate').attr('checked', false); - $('#permitEditorRemoveTemplate').attr('checked', false); + $('#permitEditorRemoveNode').attr('checked', false); $('#permitEditorModifyTemplate').attr('disabled', true); $('#permitEditorReplaceTemplate').attr('disabled', true); - $('#permitEditorRemoveTemplate').attr('disabled', true); + $('#permitEditorRemoveNode').attr('disabled', true); } else { $('#permitEditorModifyTemplate').attr('disabled', false); $('#permitEditorReplaceTemplate').attr('disabled', false); - $('#permitEditorRemoveTemplate').attr('disabled', false); + $('#permitEditorRemoveNode').attr('disabled', false); } if ($('#permitTeacherViewTemplate').attr('checked') == false) { Index: lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch02040006.sql =================================================================== diff -u -ra91a7f523070940c179e47c19a3562a20579d0ab -r2c9c32f3bdd5d45bbbf1963a071a09aa09724927 --- lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch02040006.sql (.../patch02040006.sql) (revision a91a7f523070940c179e47c19a3562a20579d0ab) +++ lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch02040006.sql (.../patch02040006.sql) (revision 2c9c32f3bdd5d45bbbf1963a071a09aa09724927) @@ -149,6 +149,11 @@ ALTER TABLE lams_planner_nodes DROP COLUMN file_uuid, DROP COLUMN file_name, ADD COLUMN ld_id BIGINT(20), ADD COLUMN teachers_permissions TINYINT UNSIGNED; +-- all statements below should be moved to a next patch file? +ALTER TABLE lams_planner_nodes CHANGE COLUMN teachers_permissions permissions INTEGER, ADD COLUMN user_id BIGINT(20), + ADD CONSTRAINT FK_lams_planner_node_user FOREIGN KEY (user_id) REFERENCES lams_user(user_id) ON DELETE SET NULL ON UPDATE SET NULL; + + -- LDEV-2664 Switch learner interface for learners if they use a mobile device insert into lams_configuration (config_key, config_value, description_key, header_name, format, required) values ('ForceMobileDevToUseFlashless','false', 'config.force.mobile.use.flashlesh', 'config.header.features', 'BOOLEAN', 1); Index: lams_common/src/java/org/lamsfoundation/lams/planner/dto/PedagogicalPlannerSequenceNodeDTO.java =================================================================== diff -u -re2e2aa63f31be07287637a2bec159d1574c0ba2c -r2c9c32f3bdd5d45bbbf1963a071a09aa09724927 --- lams_common/src/java/org/lamsfoundation/lams/planner/dto/PedagogicalPlannerSequenceNodeDTO.java (.../PedagogicalPlannerSequenceNodeDTO.java) (revision e2e2aa63f31be07287637a2bec159d1574c0ba2c) +++ lams_common/src/java/org/lamsfoundation/lams/planner/dto/PedagogicalPlannerSequenceNodeDTO.java (.../PedagogicalPlannerSequenceNodeDTO.java) (revision 2c9c32f3bdd5d45bbbf1963a071a09aa09724927) @@ -58,7 +58,7 @@ private Boolean permitEditCopy; private Boolean permitEditOriginal; private Boolean permitReplaceTemplate; - private Boolean permitRemoveTemplate; + private Boolean permitRemoveNode; // Not node-bound variables, but simply attributes used in JSP page private Boolean edit = false; @@ -77,7 +77,7 @@ } public PedagogicalPlannerSequenceNodeDTO(PedagogicalPlannerSequenceNode node, - Set subnodes, boolean isUserOwner, PedagogicalPlannerDAO dao) { + Set subnodes, boolean isSysAdmin, PedagogicalPlannerDAO dao) { if (pedagogicalPlannerDAO == null) { pedagogicalPlannerDAO = dao; } @@ -93,7 +93,7 @@ HttpSession s = SessionManager.getSession(); UserDTO user = (UserDTO) s.getAttribute(AttributeNames.USER); - setPermissions(this, isUserOwner, user, node); + setPermissions(this, isSysAdmin, user, node); this.subnodes = new LinkedList(); if (subnodes != null) { @@ -108,7 +108,7 @@ subnodeDTO.setLearningDesignTitle(subnode.getLearningDesignTitle()); subnodeDTO.setUid(subnode.getUid()); // viewing for everyone is the default setting - setPermissions(subnodeDTO, isUserOwner, user, subnode); + setPermissions(subnodeDTO, isSysAdmin, user, subnode); if (user != null) { subnodeDTO.setDisplayAddRemoveEditorsLink(subnodeDTO.isEditor); } @@ -117,10 +117,11 @@ } } - private static void setPermissions(PedagogicalPlannerSequenceNodeDTO dto, boolean isUserOwner, UserDTO user, + private static void setPermissions(PedagogicalPlannerSequenceNodeDTO dto, boolean isSysAdmin, UserDTO user, PedagogicalPlannerSequenceNode node) { // set permissions the view is based on - dto.isOwner = isUserOwner; + dto.isOwner = isSysAdmin + || (user != null && node.getUser() != null && user.getUserID().equals(node.getUser().getUserId())); dto.isEditor = dto.isOwner || (user != null && pedagogicalPlannerDAO.isEditor(user.getUserID(), node.getUid(), Role.ROLE_AUTHOR_ADMIN)); @@ -137,7 +138,7 @@ || (dto.isEditor && (nodePermissions == null || (nodePermissions & PedagogicalPlannerSequenceNode.PERMISSION_EDITOR_MODIFY) != 0)); dto.permitReplaceTemplate = dto.isOwner || (dto.isEditor && (nodePermissions == null || (nodePermissions & PedagogicalPlannerSequenceNode.PERMISSION_EDITOR_REPLACE) != 0)); - dto.permitRemoveTemplate = dto.isOwner + dto.permitRemoveNode = dto.isOwner || (dto.isEditor && (nodePermissions == null || (nodePermissions & PedagogicalPlannerSequenceNode.PERMISSION_EDITOR_REMOVE) != 0)); } @@ -317,11 +318,11 @@ this.permitReplaceTemplate = permitChangeTemplate; } - public Boolean getPermitRemoveTemplate() { - return permitRemoveTemplate; + public Boolean getPermitRemoveNode() { + return permitRemoveNode; } - public void setPermitRemoveTemplate(Boolean permitRemoveTemplate) { - this.permitRemoveTemplate = permitRemoveTemplate; + public void setPermitRemoveNode(Boolean permitRemoveTemplate) { + this.permitRemoveNode = permitRemoveTemplate; } } \ No newline at end of file