diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/ConfigurationWorkflowController.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/ConfigurationWorkflowController.java index 216072293..d70dcaf79 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/contentsections/ConfigurationWorkflowController.java +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/ConfigurationWorkflowController.java @@ -22,8 +22,13 @@ import org.libreccm.api.Identifier; import org.libreccm.api.IdentifierParser; import org.libreccm.l10n.GlobalizationHelper; import org.libreccm.security.AuthorizationRequired; +import org.libreccm.security.Role; +import org.libreccm.workflow.AssignableTask; +import org.libreccm.workflow.AssignableTaskManager; +import org.libreccm.workflow.AssignableTaskRepository; import org.libreccm.workflow.CircularTaskDependencyException; import org.libreccm.workflow.Task; +import org.libreccm.workflow.TaskAssignment; import org.libreccm.workflow.TaskManager; import org.libreccm.workflow.TaskRepository; import org.libreccm.workflow.Workflow; @@ -32,6 +37,7 @@ import org.libreccm.workflow.WorkflowRepository; import org.librecms.contentsection.ContentSection; import org.librecms.contentsection.ContentSectionManager; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Optional; @@ -66,6 +72,12 @@ public class ConfigurationWorkflowController { @Inject private AdminPermissionsChecker adminPermissionsChecker; + @Inject + private AssignableTaskManager assignableTaskManager; + + @Inject + private AssignableTaskRepository assignableTaskRepo; + /** * Used for actions involving content sections. */ @@ -834,19 +846,60 @@ public class ConfigurationWorkflowController { ) ); + if (task instanceof AssignableTask) { + final AssignableTask assignableTask = (AssignableTask) task; + final List assignedRoles = assignableTask + .getAssignments() + .stream() + .map(TaskAssignment::getRole) + .collect(Collectors.toList()); + + selectedWorkflowTaskTemplateModel.setAssignedRoles( + assignedRoles + .stream() + .map(Role::getName) + .collect(Collectors.toList()) + ); + + selectedWorkflowTaskTemplateModel.setAvailableRoles( + section + .getRoles() + .stream() + .collect(Collectors.toMap(Role::getUuid, Role::getName)) + ); + + selectedWorkflowTaskTemplateModel.setAssignedRoleKeys( + assignedRoles + .stream() + .map(Role::getUuid) + .collect(Collectors.toList()) + ); + } else { + selectedWorkflowTaskTemplateModel.setAssignedRoles( + Collections.emptyList() + ); + selectedWorkflowTaskTemplateModel.setAvailableRoles( + Collections.emptyMap() + ); + } + return "org/librecms/ui/contentsection/configuration/workflow-task.xhtml"; } /** - * Adds a task to a workflow template. + * Adds a assignable task to a workflow template. * * @param sectionIdentifierParam The identifier of the current content * section. * @param workflowIdentiferParam The identifier of the current workflow * template. * @param label The label of the new task. + * @param assignments UUIDs of the roles to which the task is + * assigned. * * @return A redirect to the details view of the workflow. + * + * @see AssignableTask */ @POST @Path("/{workflowIdentifier}/tasks/@add") @@ -855,7 +908,8 @@ public class ConfigurationWorkflowController { public String addTask( @PathParam("sectionIdentifier") final String sectionIdentifierParam, @PathParam("workflowIdentifier") final String workflowIdentiferParam, - @FormParam("label") final String label + @FormParam("label") final String label, + @FormParam("assignments") final List assignments ) { final Optional sectionResult = sectionsUi .findContentSection(sectionIdentifierParam); @@ -877,12 +931,23 @@ public class ConfigurationWorkflowController { return showWorkflowTemplateNotFound(section, workflowIdentiferParam); } final Workflow workflow = workflowResult.get(); - final Task task = new Task(); + final AssignableTask task = new AssignableTask(); task.getLabel().addValue( globalizationHelper.getNegotiatedLocale(), label ); - taskRepo.save(task); + final List roles = section + .getRoles() + .stream() + .filter(role -> assignments.contains(role.getUuid())) + .collect(Collectors.toList()); + + assignableTaskRepo.save(task); + + for (final Role role : roles) { + assignableTaskManager.assignTask(task, role); + } + taskManager.addTask(workflow, task); return String.format( @@ -1479,6 +1544,84 @@ public class ConfigurationWorkflowController { ); } + @POST + @Path("/{workflowIdentifier}/tasks/{taskIdentifier}/@assignments") + @AuthorizationRequired + @Transactional(Transactional.TxType.REQUIRED) + public String updateAssignments( + @PathParam("sectionIdentifier") final String sectionIdentifierParam, + @PathParam("workflowIdentifier") final String workflowIdentiferParam, + @PathParam("taskIdentifier") final String taskIdentifierParam, + @FormParam("assignments") final List assignments + ) { + final Optional sectionResult = sectionsUi + .findContentSection(sectionIdentifierParam); + if (!sectionResult.isPresent()) { + sectionsUi.showContentSectionNotFound(sectionIdentifierParam); + } + final ContentSection section = sectionResult.get(); + sectionModel.setSection(section); + if (!adminPermissionsChecker.canAdministerWorkflows(section)) { + return sectionsUi.showAccessDenied( + "sectionIdentifier", sectionIdentifierParam + ); + } + + final Optional workflowResult = findWorkflowTemplate( + section, workflowIdentiferParam + ); + if (!workflowResult.isPresent()) { + return showWorkflowTemplateNotFound(section, workflowIdentiferParam); + } + final Workflow workflow = workflowResult.get(); + final Optional taskResult = findTaskTemplate( + workflow, taskIdentifierParam + ); + if (!taskResult.isPresent()) { + return showWorkflowTaskTemplateNotFound( + section, workflowIdentiferParam, taskIdentifierParam + ); + } + + final Task task = taskResult.get(); + if (task instanceof AssignableTask) { + final AssignableTask assignableTask = (AssignableTask) task; + + final List assignedRoles = assignableTask + .getAssignments() + .stream() + .map(TaskAssignment::getRole) + .collect(Collectors.toList()); + + final List newAssignements = section + .getRoles() + .stream() + .filter(role -> assignments.contains(role.getUuid())) + .filter(role -> !assignedRoles.contains(role)) + .collect(Collectors.toList()); + + final List removedAssignments = assignedRoles + .stream() + .filter(role -> !assignments.contains(role.getUuid())) + .collect(Collectors.toList()); + + for (final Role role : newAssignements) { + assignableTaskManager.assignTask(assignableTask, role); + } + + for (final Role role : removedAssignments) { + assignableTaskManager.retractTask(assignableTask, role); + } + } + + return String.format( + "redirect:/%s/configuration/workflows/%s/tasks/%s", + sectionIdentifierParam, + workflowIdentiferParam, + taskIdentifierParam + ); + } + /** * Helper method for retrieving a workflow template. * diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/ContentSectionApplication.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/ContentSectionApplication.java index f3353bad3..1e1b4afc3 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/contentsections/ContentSectionApplication.java +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/ContentSectionApplication.java @@ -28,16 +28,13 @@ import org.librecms.ui.contentsections.documents.DocumentWorkflowController; import org.librecms.ui.contentsections.documents.MvcAuthoringSteps; import java.util.HashSet; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import javax.enterprise.inject.Any; import javax.enterprise.inject.Instance; import javax.inject.Inject; -import javax.mvc.Controller; import javax.ws.rs.ApplicationPath; -import javax.ws.rs.Path; import javax.ws.rs.core.Application; /** @@ -73,11 +70,11 @@ public class ContentSectionApplication extends Application { classes.add(ContentSectionController.class); classes.add(DocumentFolderController.class); classes.add(DocumentController.class); - - classes.addAll(getAuthoringSteps()); - classes.add(DocumentLifecyclesController.class); classes.add(DocumentWorkflowController.class); + + classes.addAll(getAuthoringSteps()); + classes.add(IsAuthenticatedFilter.class); return classes; diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/SelectedWorkflowTaskTemplateModel.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/SelectedWorkflowTaskTemplateModel.java index 4c64d83c8..8bc405476 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/contentsections/SelectedWorkflowTaskTemplateModel.java +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/SelectedWorkflowTaskTemplateModel.java @@ -99,6 +99,14 @@ public class SelectedWorkflowTaskTemplateModel { */ private Map noneBlockingTasks; + private List assignedRoles; + + private List assignedRoleKeys; + + private Map availableRoles; + + + public long getTaskId() { return taskId; } @@ -196,4 +204,33 @@ public class SelectedWorkflowTaskTemplateModel { this.noneBlockingTasks = new HashMap<>(noneBlockingTasks); } + public Map getAvailableRoles() { + return Collections.unmodifiableMap(availableRoles); + } + + public void setAvailableRoles( + final Map availableRoles + ) { + this.availableRoles = new HashMap<>(availableRoles); + } + + public List getAssignedRoles() { + return Collections.unmodifiableList(assignedRoles); + } + + public void setAssignedRoles(final List assignedRoles) { + this.assignedRoles = new ArrayList<>(assignedRoles); + } + + public List getAssignedRoleKeys() { + return Collections.unmodifiableList(assignedRoleKeys); + } + + public void setAssignedRoleKeys(final List assignedRoleKeys) { + this.assignedRoleKeys = new ArrayList<>(assignedRoleKeys); + } + + + + } diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/AbstractMvcAuthoringStep.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/AbstractMvcAuthoringStep.java index b4d9531c6..8ad84a26d 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/AbstractMvcAuthoringStep.java +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/AbstractMvcAuthoringStep.java @@ -26,12 +26,12 @@ import org.librecms.contentsection.ContentItemRepository; import org.librecms.contentsection.ContentSection; import org.librecms.ui.contentsections.ContentSectionModel; import org.librecms.ui.contentsections.ContentSectionsUi; +import org.librecms.ui.contentsections.ItemPermissionChecker; import java.util.HashMap; import java.util.Map; import java.util.Optional; -import javax.annotation.PostConstruct; import javax.inject.Inject; import javax.mvc.Models; import javax.servlet.http.HttpServletRequest; @@ -52,6 +52,9 @@ public abstract class AbstractMvcAuthoringStep implements MvcAuthoringStep { @Inject private ContentItemManager itemManager; + @Inject + private ItemPermissionChecker itemPermissionChecker; + @Inject private ContentItemRepository itemRepo; @@ -108,7 +111,7 @@ public abstract class AbstractMvcAuthoringStep implements MvcAuthoringStep { ) ); sectionModel.setSection(contentSection); - + document = itemRepo .findByPath(contentSection, documentPathParam) .orElseThrow( @@ -177,6 +180,20 @@ public abstract class AbstractMvcAuthoringStep implements MvcAuthoringStep { ); } + @Override + public boolean getCanEdit() { + if (!itemPermissionChecker.canEditItem(document)) { + return false; + } + + if (documentModel.getCurrentTask() == null) { + return false; + } + + return documentModel.getCurrentTask().isAssignedToCurrentUser() + || itemPermissionChecker.canAdministerItems(document); + } + @Override public String getLabel() { return Optional @@ -210,6 +227,55 @@ public abstract class AbstractMvcAuthoringStep implements MvcAuthoringStep { documentPath = itemManager.getItemPath(document).substring(1); // Without leading slash } + @Override + public String getStepPath() { + final ContentSection section = Optional + .ofNullable(contentSection) + .orElseThrow( + () -> new WebApplicationException( + String.format( + "Authoring Step %s was not initalized properly. " + + "Did you forget to call %s#init()?", + getStepClass().getName(), + AbstractMvcAuthoringStep.class.getName() + ) + ) + ); + final String docPath = Optional + .ofNullable(documentPath) + .orElseThrow( + () -> new WebApplicationException( + String.format( + "Authoring Step %s was not initalized properly. " + + "Did you forget to call %s#init()?", + getStepClass().getName(), + AbstractMvcAuthoringStep.class.getName() + ) + ) + ); + + final Map values = new HashMap<>(); + values.put( + MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM, + section.getLabel() + ); + values.put( + MvcAuthoringSteps.DOCUMENT_PATH_PATH_PARAM_NAME, + docPath + ); + + return Optional + .ofNullable(getStepClass().getAnnotation(Path.class)) + .map(Path::value) + .map( + path -> UriBuilder + .fromPath(path) + .buildFromMap(values) + .toString() + ) + .orElse(""); + } + @Override public String buildRedirectPathForStep() { final ContentSection section = Optional @@ -236,7 +302,7 @@ public abstract class AbstractMvcAuthoringStep implements MvcAuthoringStep { ) ) ); - + final Map values = new HashMap<>(); values.put( MvcAuthoringSteps.SECTION_IDENTIFIER_PATH_PARAM, @@ -254,6 +320,7 @@ public abstract class AbstractMvcAuthoringStep implements MvcAuthoringStep { path -> UriBuilder .fromPath(path) .buildFromMap(values) + .toString() ) .map(path -> String.format("redirect:%s", path)) .orElse(""); @@ -304,6 +371,7 @@ public abstract class AbstractMvcAuthoringStep implements MvcAuthoringStep { .fromPath(path) .path(subPath) .buildFromMap(values) + .toString() ) .map(path -> String.format("redirect:%s", path)) .orElse(""); diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/DocumentWorkflowController.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/DocumentWorkflowController.java index 95c173dc4..7b7e0e10e 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/DocumentWorkflowController.java +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/DocumentWorkflowController.java @@ -132,7 +132,7 @@ public class DocumentWorkflowController { @AuthorizationRequired @Transactional(Transactional.TxType.REQUIRED) public String lockTask( - @PathParam("sectionIdentifider") final String sectionIdentifier, + @PathParam("sectionIdentifier") final String sectionIdentifier, @PathParam("documentPath") final String documentPath, @PathParam("taskIdentifier") final String taskIdentifier, @FormParam("returnUrl") final String returnUrl @@ -189,7 +189,7 @@ public class DocumentWorkflowController { @AuthorizationRequired @Transactional(Transactional.TxType.REQUIRED) public String unlockTask( - @PathParam("sectionIdentifider") final String sectionIdentifier, + @PathParam("sectionIdentifier") final String sectionIdentifier, @PathParam("documentPath") final String documentPath, @PathParam("taskIdentifier") final String taskIdentifier, @FormParam("returnUrl") final String returnUrl @@ -247,7 +247,7 @@ public class DocumentWorkflowController { @AuthorizationRequired @Transactional(Transactional.TxType.REQUIRED) public String finishTask( - @PathParam("sectionIdentifider") final String sectionIdentifier, + @PathParam("sectionIdentifier") final String sectionIdentifier, @PathParam("documentPath") final String documentPath, @PathParam("taskIdentifier") final String taskIdentifier, @FormParam("comment") @DefaultValue("") final String comment, @@ -310,7 +310,7 @@ public class DocumentWorkflowController { @AuthorizationRequired @Transactional(Transactional.TxType.REQUIRED) public String applyAlternateWorkflow( - @PathParam("sectionIdentifider") final String sectionIdentifier, + @PathParam("sectionIdentifier") final String sectionIdentifier, @PathParam("documentPath") final String documentPath, @FormParam("newWorkflowUuid") final String newWorkflowUuid, @FormParam("returnUrl") final String returnUrl @@ -362,6 +362,27 @@ public class DocumentWorkflowController { return String.format("redirect:%s", returnUrl); } + /** + * Starts the workflow assigned to an content item. + * + * @param sectionIdentifier The identifier of the current content section. + * @param documentPath The path of the current document. + * @param returnUrl The URL to return to. + * + * @return A redirect to the {@code returnUrl}. + */ + @POST + @Path("/@start") + @AuthorizationRequired + @Transactional(Transactional.TxType.REQUIRED) + public String startWorkflow( + @PathParam("sectionIdentifier") final String sectionIdentifier, + @PathParam("documentPath") final String documentPath, + @FormParam("returnUrl") final String returnUrl + ) { + return restartWorkflow(sectionIdentifier, documentPath, returnUrl); + } + /** * Restarts the workflow assigned to an content item. * @@ -376,7 +397,7 @@ public class DocumentWorkflowController { @AuthorizationRequired @Transactional(Transactional.TxType.REQUIRED) public String restartWorkflow( - @PathParam("sectionIdentifider") final String sectionIdentifier, + @PathParam("sectionIdentifier") final String sectionIdentifier, @PathParam("documentPath") final String documentPath, @FormParam("returnUrl") final String returnUrl ) { diff --git a/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/MvcAuthoringStep.java b/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/MvcAuthoringStep.java index 8de1c9518..d585fecf8 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/MvcAuthoringStep.java +++ b/ccm-cms/src/main/java/org/librecms/ui/contentsections/documents/MvcAuthoringStep.java @@ -42,6 +42,20 @@ public interface MvcAuthoringStep { String getDocumentPath() throws ContentSectionNotFoundException, DocumentNotFoundException; + /** + * Can the current user edit the document. This method MUST only return + * {@code true} if + *
    + *
  • The current user has the permission to edit the item.
  • + *
  • The item has an active task.
  • + *
  • The task is assigned to the current user or the current user has + * admin priviliges for the content section of the item.
  • + *
+ * + * @return {@code true} if the current user can edit the document/item, {@false} otherwise. + */ + boolean getCanEdit(); + /** * Gets the label for an authoring step. * @@ -73,6 +87,8 @@ public interface MvcAuthoringStep { void updateDocumentPath() throws ContentSectionNotFoundException, DocumentNotFoundException; + String getStepPath(); + /** * Builds the redirect path of the authoring step.This path is most often * used to implement the redirect after post pattern. diff --git a/ccm-cms/src/main/java/org/librecms/ui/contenttypes/MvcArticleCreateStep.java b/ccm-cms/src/main/java/org/librecms/ui/contenttypes/MvcArticleCreateStep.java index 9e1c37efd..1b48c9cc3 100644 --- a/ccm-cms/src/main/java/org/librecms/ui/contenttypes/MvcArticleCreateStep.java +++ b/ccm-cms/src/main/java/org/librecms/ui/contenttypes/MvcArticleCreateStep.java @@ -285,7 +285,7 @@ public class MvcArticleCreateStep itemRepo.save(article); return String.format( - "redirect:/%s/documents/%s/%s/@articleproperties", + "redirect:/%s/documents/%s/%s/@article-basicproperties", getContentSectionLabel(), getFolderPath(), name diff --git a/ccm-cms/src/main/resources/WEB-INF/views/org/librecms/ui/contentsection/configuration/workflow-task.xhtml b/ccm-cms/src/main/resources/WEB-INF/views/org/librecms/ui/contentsection/configuration/workflow-task.xhtml index d36c2d42d..ac3242560 100644 --- a/ccm-cms/src/main/resources/WEB-INF/views/org/librecms/ui/contentsection/configuration/workflow-task.xhtml +++ b/ccm-cms/src/main/resources/WEB-INF/views/org/librecms/ui/contentsection/configuration/workflow-task.xhtml @@ -112,6 +112,79 @@ values="#{SelectedWorkflowTaskTemplateModel.description}" /> +

#{CmsAdminMessages['contentsection.configuration.workflows.task_details.assigned_to.title']}

+
+
+ +
+ + +

+ #{CmsAdminMessages['contentsection.configuration.workflows.task_details.not_assigned']} +

+
+ +
    + +
  • #{role}
  • +
    +
+
+
+
+ +

#{CmsAdminMessages['contentsection.configuration.workflows.task_details.blocking_tasks.title']}

@@ -139,7 +212,7 @@ id="add-blocking-task-dialog-title"> #{CmsAdminMessages['contentsection.configuration.workflows.task_details.blocking_tasks.add.dialog.title']} -
- + - + + + + + + + + - - - - - - - - - + - - - - -
+ #{CmsAdminMessages['contentsection.configuration.workflow.tasks.table.cols.label']} + + #{CmsAdminMessages['contentsection.configuration.workflow.tasks.table.cols.actions']} +
- #{CmsAdminMessages['contentsection.configuration.workflow.tasks.table.cols.label']} - - #{CmsAdminMessages['contentsection.configuration.workflow.tasks.table.cols.actions']} -
- - #{task.label} - - - - + + #{task.label} + + + + - -
+ + + + + + + + + diff --git a/ccm-cms/src/main/resources/WEB-INF/views/org/librecms/ui/contentsection/documents/authoringstep.xhtml b/ccm-cms/src/main/resources/WEB-INF/views/org/librecms/ui/contentsection/documents/authoringstep.xhtml index 89223960b..9a6177890 100644 --- a/ccm-cms/src/main/resources/WEB-INF/views/org/librecms/ui/contentsection/documents/authoringstep.xhtml +++ b/ccm-cms/src/main/resources/WEB-INF/views/org/librecms/ui/contentsection/documents/authoringstep.xhtml @@ -9,13 +9,13 @@