Index: lams_central/src/java/org/lamsfoundation/lams/web/planner/PedagogicalPlannerAction.java =================================================================== RCS file: /usr/local/cvsroot/lams_central/src/java/org/lamsfoundation/lams/web/planner/PedagogicalPlannerAction.java,v diff -u -r1.18.2.3.2.1 -r1.18.2.3.2.2 --- lams_central/src/java/org/lamsfoundation/lams/web/planner/PedagogicalPlannerAction.java 11 Feb 2010 00:32:59 -0000 1.18.2.3.2.1 +++ lams_central/src/java/org/lamsfoundation/lams/web/planner/PedagogicalPlannerAction.java 25 Feb 2010 19:21:58 -0000 1.18.2.3.2.2 @@ -35,10 +35,12 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.Vector; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; @@ -100,6 +102,7 @@ import org.lamsfoundation.lams.usermanagement.Role; import org.lamsfoundation.lams.usermanagement.User; import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.usermanagement.exception.UserAccessDeniedException; import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; import org.lamsfoundation.lams.util.CentralConstants; import org.lamsfoundation.lams.util.CentralToolContentHandler; @@ -133,6 +136,7 @@ * @struts:action path="/pedagogicalPlanner/grouping" scope="request" name="PedagogicalPlannerGroupingForm" * validate="false" parameter="method" * @struts:action-forward name="grouping" path="/pedagogical_planner/grouping.jsp" + * @struts:action-forward name="editAuthors" path="/pedagogical_planner/editAuthors.jsp" */ public class PedagogicalPlannerAction extends LamsDispatchAction { @@ -220,6 +224,18 @@ private static final Map filterLanguageMap = new TreeMap(); private static final Map filterStopWordsMap = new TreeMap(); + // Tutorial video page string for recognising which page the video was started from + private static final String PAGE_STRING_START_PLANNER = "StPed"; + + // for tutorial videos + private static final String ATTR_PAGE_STR = "pageString"; + private static final String ATTR_DO_NOT_SHOW_AGAIN = "doNotShowAgain"; + private static final String ATTR_SHOW_TUTORIAL = "showTutorial"; + + private static final String PARAM_USER_ID = "userId"; + + private static final int PLANNER_RECENT_LD_MAX_COUNT = 10; + static { PedagogicalPlannerAction.filterLanguageMap.put("en", "English"); PedagogicalPlannerAction.filterLanguageMap.put("nl", "Dutch"); @@ -246,15 +262,28 @@ */ public ActionForward unspecified(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { + + // First we check if a tutorial video should be displayed + HttpSession session = SessionManager.getSession(); + UserDTO userDto = (UserDTO) session.getAttribute(AttributeNames.USER); + + boolean doNotShowAgain = false;//userDto.getPagesWithDisabledTutorials() != null + //&& userDto.getPagesWithDisabledTutorials().contains(PedagogicalPlannerAction.PAGE_STRING_START_PLANNER); + boolean showTutorial = false;//!(userDto.getTutorialsDisabled() || doNotShowAgain); + + request.setAttribute(PedagogicalPlannerAction.ATTR_PAGE_STR, PedagogicalPlannerAction.PAGE_STRING_START_PLANNER); + request.setAttribute(PedagogicalPlannerAction.ATTR_SHOW_TUTORIAL, showTutorial); + request.setAttribute(PedagogicalPlannerAction.ATTR_DO_NOT_SHOW_AGAIN, doNotShowAgain); + return openSequenceNode(mapping, form, request, response); } /*----------------------- TEMPLATE CHOOSER METHODS --------------------*/ /** - * Opens template from sequence chooser. + * Opens a new template from sequence chooser. */ - public ActionForward openTemplate(ActionMapping mapping, ActionForm form, HttpServletRequest request, + public ActionForward openNewTemplate(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws ServletException { ActionMessages errors = new ActionMessages(); @@ -275,6 +304,26 @@ } /** + * Opens an existing learning design. + */ + public ActionForward openExistingTemplate(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws ServletException { + + Long learningDesignId = WebUtil.readLongParam(request, CentralConstants.PARAM_LEARNING_DESIGN_ID); + + // Open the learning design stored in DB. + LearningDesign learningDesign = getAuthoringService().getLearningDesign(learningDesignId); + ActionMessages errors = openTemplate(request, learningDesign); + + if (!errors.isEmpty()) { + saveErrors(request, errors); + // If anything goes wrong, errors will be displayed at top. This approach is used widely in this action. + return openSequenceNode(mapping, form, request, (Long) null); + } + return mapping.findForward(PedagogicalPlannerAction.FORWARD_TEMPLATE); + } + + /** * The main method for opening and parsing template (chosen learning desing). * * @param request @@ -317,18 +366,23 @@ } } // create DTO for the whole design + Long nodeUid = WebUtil.readLongParam(request, CentralConstants.PARAM_UID, true); + PedagogicalPlannerTemplateDTO planner = new PedagogicalPlannerTemplateDTO(); planner.setActivitySupportingPlannerCount(activitySupportingPlannerCount); planner.setSequenceTitle(learningDesign.getTitle()); planner.setActivities(activities); planner.setLearningDesignID(learningDesign.getLearningDesignId()); + planner.setNodeUid(nodeUid); // Some additional options for submitting activity forms; should be moved to configuration file in the future planner.setSendInPortions(false); planner.setSubmitDelay(5000); planner.setActivitiesPerPortion(2); request.setAttribute(CentralConstants.ATTR_PLANNER, planner); + + updateRecentLearningDesignList(learningDesign.getLearningDesignId()); return errors; } @@ -437,11 +491,11 @@ // Iterate through all the activities from the sequence, adding them to Planner activity set do { addedDTO = addActivityToPlanner(learningDesign, activities, nestedActivity); - Transition transitionTo = nestedActivity.getTransitionTo(); - if (transitionTo == null) { + Transition transitionFrom = nestedActivity.getTransitionFrom(); + if (transitionFrom == null) { nestedActivity = null; } else { - nestedActivity = transitionTo.getToActivity(); + nestedActivity = transitionFrom.getToActivity(); } addedDTO.setParentActivityTitle(activity.getTitle()); addedDTO.setGroup(branch); @@ -561,9 +615,8 @@ getMonitoringService().startLesson(lesson.getLessonId(), userDto.getUserID()); String newPath = mapping.findForward(PedagogicalPlannerAction.FORWARD_PREVIEW).getPath(); newPath = newPath + PedagogicalPlannerAction.CHAR_AMPERSAND + AttributeNames.PARAM_LESSON_ID - + PedagogicalPlannerAction.CHAR_EQUALS + lesson.getLessonId() - + PedagogicalPlannerAction.CHAR_AMPERSAND + AttributeNames.PARAM_MODE - + PedagogicalPlannerAction.CHAR_EQUALS + "preview"; + + PedagogicalPlannerAction.CHAR_EQUALS + lesson.getLessonId() + PedagogicalPlannerAction.CHAR_AMPERSAND + + AttributeNames.PARAM_MODE + PedagogicalPlannerAction.CHAR_EQUALS + "preview"; return new ActionForward(newPath, true); } @@ -597,10 +650,6 @@ */ public ActionForward openSequenceNode(ActionMapping mapping, ActionForm form, HttpServletRequest request, Long nodeUid) throws ServletException { - // Only SysAdmin can open the editor - Boolean isSysAdmin = request.isUserInRole(Role.SYSADMIN); - Boolean edit = WebUtil.readBooleanParam(request, CentralConstants.PARAM_EDIT, false); - edit &= isSysAdmin; String filterText = WebUtil.readStrParam(request, CentralConstants.PARAM_FILTER_TEXT, true); // Do we display the root (top) node or an existing one PedagogicalPlannerSequenceNode node = null; @@ -611,6 +660,11 @@ } PedagogicalPlannerAction.log.debug("Opening sequence node with UID: " + nodeUid); + // Only certain roles can open the editor + Boolean hasRole = hasRole(request, nodeUid); + Boolean edit = WebUtil.readBooleanParam(request, CentralConstants.PARAM_EDIT, false); + edit &= hasRole; + // Fill the DTO PedagogicalPlannerSequenceNodeDTO dto = null; if (filterText != null) { @@ -628,7 +682,7 @@ filteredNodes.add(subnode); } - dto = new PedagogicalPlannerSequenceNodeDTO(node, filteredNodes); + dto = new PedagogicalPlannerSequenceNodeDTO(node, filteredNodes, request.isUserInRole(Role.SYSADMIN), getPedagogicalPlannerDAO()); for (PedagogicalPlannerSequenceNodeDTO subnodeDTO : dto.getSubnodes()) { List titlePath = getPedagogicalPlannerDAO().getTitlePath(subnodeDTO.getUid()); subnodeDTO.setTitlePath(titlePath); @@ -645,7 +699,10 @@ if (dto == null) { // No filtering or something went wrong in filtering - dto = new PedagogicalPlannerSequenceNodeDTO(node, node.getSubnodes()); + dto = new PedagogicalPlannerSequenceNodeDTO(node, node.getSubnodes(), request.isUserInRole(Role.SYSADMIN), getPedagogicalPlannerDAO()); + if (nodeUid == null) { + dto.setRecentlyModifiedNodes(getRecentlyModifiedLearnindDesignsAsNodes()); + } } // Additional DTO parameters @@ -654,7 +711,7 @@ Boolean importNode = WebUtil.readBooleanParam(request, CentralConstants.PARAM_IMPORT_NODE, false); dto.setCreateSubnode(createSubnode); dto.setEdit(edit); - dto.setIsSysAdmin(isSysAdmin); + dto.setHasRole(hasRole); dto.setImportNode(importNode); dto.setTitlePath(titlePath); @@ -705,20 +762,28 @@ PedagogicalPlannerSequenceNode node = null; PedagogicalPlannerSequenceNodeForm nodeForm = (PedagogicalPlannerSequenceNodeForm) form; + boolean newRootNode = false; if (nodeUid == null) { // It's a new subnode node = new PedagogicalPlannerSequenceNode(); Long parentUid = nodeForm.getParentUid() == 0 ? null : nodeForm.getParentUid(); if (parentUid != null) { PedagogicalPlannerSequenceNode parent = getPedagogicalPlannerDAO().getByUid(parentUid); node.setParent(parent); + } else { + newRootNode = true; } node.setOrder(getPedagogicalPlannerDAO().getNextOrderId(parentUid)); } else { // It's an existing node node = getPedagogicalPlannerDAO().getByUid(nodeUid); nodeUid = node.getUid(); } + + if (!hasRole(request, nodeUid)) { + log.debug("Unauthorised attempt to saveSequenceNode"); + throw new UserAccessDeniedException(); + } PedagogicalPlannerAction.log.debug("Saving sequence node with UID: " + nodeUid); // If anything goes wrong, we need to put back these values @@ -776,6 +841,16 @@ getPedagogicalPlannerDAO().saveOrUpdateNode(node); // If it was a new subnode, we need to retrieved the assigned UID nodeUid = node.getUid(); + // If it was a new root node, add creator's role + if (newRootNode) { + try { + HttpSession s = SessionManager.getSession(); + UserDTO u = (UserDTO) s.getAttribute(AttributeNames.USER); + getPedagogicalPlannerDAO().saveNodeRole(u.getUserID(), nodeUid, Role.ROLE_AUTHOR_ADMIN); + } catch (Exception e) { + log.error("Error saving role for newly created root node: " + e.getMessage()); + } + } } catch (RepositoryCheckedException e) { errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage( @@ -871,14 +946,21 @@ * @return * @throws IOException * @throws ServletException + * @throws UserAccessDeniedException */ public ActionForward removeSequenceNode(ActionMapping mapping, ActionForm form, HttpServletRequest request, - HttpServletResponse response) throws IOException, ServletException { + HttpServletResponse response) throws IOException, ServletException, UserAccessDeniedException { Long nodeUid = WebUtil.readLongParam(request, CentralConstants.PARAM_UID); PedagogicalPlannerSequenceNode node = getPedagogicalPlannerDAO().getByUid(nodeUid); Long parentUid = node.getParent() == null ? null : node.getParent().getUid(); - PedagogicalPlannerAction.log.debug("Removing sequence node with UID" + nodeUid); - getPedagogicalPlannerDAO().removeNode(node); + + if (hasRole(request, nodeUid)) { + PedagogicalPlannerAction.log.debug("Removing sequence node with UID" + nodeUid); + getPedagogicalPlannerDAO().removeNode(node); + } else { + log.debug("Unauthorised attempt to removeSequenceNode"); + throw new UserAccessDeniedException(); + } return openSequenceNode(mapping, form, request, parentUid); } @@ -1032,6 +1114,11 @@ public ActionForward importNode(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws ServletException { + if (!hasRole(request, null)) { + log.debug("Unauthorised access to importNode"); + throw new UserAccessDeniedException(); + } + PedagogicalPlannerSequenceNodeForm nodeForm = (PedagogicalPlannerSequenceNodeForm) form; ActionMessages errors = validateFormFile(nodeForm); @@ -1419,6 +1506,59 @@ return mapping.findForward(PedagogicalPlannerAction.FORWARD_GROUPING); } + private List getRecentlyModifiedLearnindDesignsAsNodes() { + // Add the recently modified learning design list, if it's the root node with no filtering + HttpSession session = SessionManager.getSession(); + UserDTO userDto = (UserDTO) session.getAttribute(AttributeNames.USER); + User user = (User) getUserManagementService().findById(User.class, userDto.getUserID()); + // the list is sorted most-recently-edited-on-top (so by the timestamp descending) + Set recentLDs = user.getRecentlyModifiedLearningDesigns(); + List recentNodes = new LinkedList(); + // create "dummy", almost empty nodes + for (Long learningDesignId : recentLDs) { + LearningDesign learningDesign = getAuthoringService().getLearningDesign(learningDesignId); + + PedagogicalPlannerSequenceNodeDTO node = new PedagogicalPlannerSequenceNodeDTO(); + node.setTitle(learningDesign.getTitle()); + node.setLearningDesignId(learningDesignId); + + recentNodes.add(node); + } + return recentNodes; + } + + /** + * Adds the Learning Design to the list of recenlty edited sequences. It puts the selected LD on the top of the + * list. + * + * @param learningDesignId + */ + private void updateRecentLearningDesignList(Long learningDesignId) { + HttpSession session = SessionManager.getSession(); + UserDTO userDto = (UserDTO) session.getAttribute(AttributeNames.USER); + User user = (User) getUserManagementService().findById(User.class, userDto.getUserID()); + // the list is sorted most-recently-edited-on-top (so by the timestamp descending) + Set recentLDs = user.getRecentlyModifiedLearningDesigns(); + boolean ldFound = false; + // if LD is already in the list, remove it and add it again - it a way to refresh the timestamp in DB + Iterator iter = recentLDs.iterator(); + while (iter.hasNext()) { + Long recentLD = iter.next(); + if (recentLD.equals(learningDesignId)) { + iter.remove(); + getUserManagementService().save(user); + ldFound = true; + break; + } + } + // if LD was not in the list, but the list is full, remove the last entry + if (!ldFound && recentLDs.size() >= PedagogicalPlannerAction.PLANNER_RECENT_LD_MAX_COUNT) { + iter.remove(); + } + recentLDs.add(learningDesignId); + getUserManagementService().save(user); + } + /*-------------------------- TEMPLATE BASE METHODS -----------------*/ /** @@ -1504,9 +1644,106 @@ writeOutFile(response, zipFilePath); return null; } + + public ActionForward addRemoveEditors(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws Exception { + Long nodeUid = WebUtil.readLongParam(request, CentralConstants.PARAM_UID, true); + if (hasRole(request, nodeUid)) { + List existingUsers = getPedagogicalPlannerDAO().getNodeUsers(nodeUid, Role.ROLE_AUTHOR_ADMIN); + + Integer orgId = getUserManagementService().getRootOrganisation().getOrganisationId(); + Vector potentialUsersVector = getUserManagementService().getUsersFromOrganisationByRole(orgId, + Role.AUTHOR_ADMIN, false, true); + + // list existing users (inherited role from parent nodes) + Set allInheritedUsers = getPedagogicalPlannerDAO().getInheritedNodeUsers(nodeUid, Role.ROLE_AUTHOR_ADMIN); + ArrayList filteredInheritedUsers = new ArrayList(); + for (Object o : allInheritedUsers) { + User u = (User) o; + // filter existing users of the actual node + if (existingUsers.contains(u)) { + continue; + } + filteredInheritedUsers.add(u); + } + + // filter existing users from list of potential users + ArrayList potentialUsers = new ArrayList(); + for (Object o : potentialUsersVector) { + User u = (User) o; + if (existingUsers.contains(u) || allInheritedUsers.contains(u)) { + continue; + } + // filter self + if (StringUtils.equals(u.getLogin(), request.getRemoteUser())) { + continue; + } + potentialUsers.add(u); + } + + request.setAttribute("existingUsers", existingUsers); + request.setAttribute("potentialUsers", potentialUsers); + request.setAttribute("inheritedUsers", filteredInheritedUsers); + + return mapping.findForward("editAuthors"); + } else { + log.debug("Unauthorised attempt to access add/remove editors page."); + throw new UserAccessDeniedException(); + } + } + + public ActionForward addEditor(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws Exception { + Integer userId = WebUtil.readIntParam(request, PedagogicalPlannerAction.PARAM_USER_ID, false); + Long nodeUid = WebUtil.readLongParam(request, CentralConstants.PARAM_UID, true); + + if (hasRole(request, nodeUid)) { + getPedagogicalPlannerDAO().saveNodeRole(userId, nodeUid, Role.ROLE_AUTHOR_ADMIN); + } else { + log.debug("Unauthorised attempt to add editor to node."); + } + + return null; + } + + public ActionForward removeEditor(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws Exception { + Integer userId = WebUtil.readIntParam(request, PedagogicalPlannerAction.PARAM_USER_ID, false); + Long nodeUid = WebUtil.readLongParam(request, CentralConstants.PARAM_UID, false); + + if (hasRole(request, nodeUid)) { + getPedagogicalPlannerDAO().removeNodeRole(userId, nodeUid, Role.ROLE_AUTHOR_ADMIN); + } else { + log.debug("Unauthorised attempt to remove editor from node."); + } + + return null; + } + /*------------------------ COMMON METHODS --------------------*/ + // only these roles can edit nodes and give this role on this node to others + private Boolean hasRole(HttpServletRequest request, Long nodeUid) { + if (request.isUserInRole(Role.SYSADMIN)) { + // sysadmins have all permission + return true; + } else { + if (nodeUid == null) { + // 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); + } + } + } + + private Boolean isNodeEditor(HttpServletRequest request, Long nodeUid) { + User user = (User) getUserManagementService().getUserByLogin(request.getRemoteUser()); + return getPedagogicalPlannerDAO().isEditor(user.getUserId(), nodeUid, Role.ROLE_AUTHOR_ADMIN); + } + private IExportToolContentService getExportService() { if (PedagogicalPlannerAction.exportService == null) { WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(getServlet() @@ -1614,4 +1851,5 @@ } } } + } \ No newline at end of file Fisheye: Tag 1.3.2.1 refers to a dead (removed) revision in file `lams_central/web/tutorialVideo.jsp'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_central/web/includes/javascript/pedagogicalPlanner.js =================================================================== RCS file: /usr/local/cvsroot/lams_central/web/includes/javascript/pedagogicalPlanner.js,v diff -u -r1.13 -r1.13.6.1 --- lams_central/web/includes/javascript/pedagogicalPlanner.js 21 Mar 2009 10:35:17 -0000 1.13 +++ lams_central/web/includes/javascript/pedagogicalPlanner.js 25 Feb 2010 19:26:51 -0000 1.13.6.1 @@ -168,7 +168,7 @@ } } - function leaveNodeEditor(text,url){ + function leavePageAfterConfirm(text,url){ if (text==null || confirm(text)){ document.location.href=url; }