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}"
/>
+