Several bugfixes for workflow and task handling

pull/10/head
Jens Pelzetter 2021-05-01 17:55:30 +02:00
parent b140923d86
commit 2be640f8d4
15 changed files with 735 additions and 318 deletions

View File

@ -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<Role> 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<String> assignments
) {
final Optional<ContentSection> 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<Role> 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<String> assignments
) {
final Optional<ContentSection> 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<Workflow> workflowResult = findWorkflowTemplate(
section, workflowIdentiferParam
);
if (!workflowResult.isPresent()) {
return showWorkflowTemplateNotFound(section, workflowIdentiferParam);
}
final Workflow workflow = workflowResult.get();
final Optional<Task> 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<Role> assignedRoles = assignableTask
.getAssignments()
.stream()
.map(TaskAssignment::getRole)
.collect(Collectors.toList());
final List<Role> newAssignements = section
.getRoles()
.stream()
.filter(role -> assignments.contains(role.getUuid()))
.filter(role -> !assignedRoles.contains(role))
.collect(Collectors.toList());
final List<Role> 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.
*

View File

@ -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;

View File

@ -99,6 +99,14 @@ public class SelectedWorkflowTaskTemplateModel {
*/
private Map<String, String> noneBlockingTasks;
private List<String> assignedRoles;
private List<String> assignedRoleKeys;
private Map<String, String> availableRoles;
public long getTaskId() {
return taskId;
}
@ -196,4 +204,33 @@ public class SelectedWorkflowTaskTemplateModel {
this.noneBlockingTasks = new HashMap<>(noneBlockingTasks);
}
public Map<String, String> getAvailableRoles() {
return Collections.unmodifiableMap(availableRoles);
}
public void setAvailableRoles(
final Map<String, String> availableRoles
) {
this.availableRoles = new HashMap<>(availableRoles);
}
public List<String> getAssignedRoles() {
return Collections.unmodifiableList(assignedRoles);
}
public void setAssignedRoles(final List<String> assignedRoles) {
this.assignedRoles = new ArrayList<>(assignedRoles);
}
public List<String> getAssignedRoleKeys() {
return Collections.unmodifiableList(assignedRoleKeys);
}
public void setAssignedRoleKeys(final List<String> assignedRoleKeys) {
this.assignedRoleKeys = new ArrayList<>(assignedRoleKeys);
}
}

View File

@ -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<String, String> 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<String, String> 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("");

View File

@ -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
) {

View File

@ -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
* <ul>
* <li>The current user has the permission to edit the item.</li>
* <li>The item has an active task.</li>
* <li>The task is assigned to the current user or the current user has
* admin priviliges for the content section of the item.</li>
* </ul>
*
* @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.

View File

@ -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

View File

@ -112,6 +112,79 @@
values="#{SelectedWorkflowTaskTemplateModel.description}"
/>
<h2>#{CmsAdminMessages['contentsection.configuration.workflows.task_details.assigned_to.title']}</h2>
<div class="mb-2">
<div class="text-right">
<button class="btn btn-primary"
data-toggle="modal"
data-target="#update-assignments-dialog"
type="button">
<bootstrap:svgIcon icon="person-plus" />
<span>#{CmsAdminMessages['contentsection.configuration.workflows.task_details.assigned_to.edit']}</span>
</button>
</div>
<c:choose>
<c:when test="#{SelectedWorkflowTaskTemplateModel.assignedRoles.isEmpty()}">
<p>
#{CmsAdminMessages['contentsection.configuration.workflows.task_details.not_assigned']}
</p>
</c:when>
<c:otherwise>
<ul>
<c:forEach items="#{SelectedWorkflowTaskTemplateModel.assignedRoles}"
var="role">
<li>#{role}</li>
</c:forEach>
</ul>
</c:otherwise>
</c:choose>
</div>
<div aria-hidden="true"
aria-labelledby="update-assignments-dialog-title"
class="modal fade"
id="update-assignments-dialog"
tabindex="-1">
<div class="modal-dialog">
<form action="#{mvc.basePath}/#{ContentSectionModel.sectionName}/configuration/workflows/UUID-#{SelectedWorkflowTemplateModel.uuid}/tasks/UUID-#{SelectedWorkflowTaskTemplateModel.uuid}/@assignments"
class="modal-content"
method="post">
<div class="modal-header">
<h3 class="modal-title"
id="update-assignments-modal-title">
#{CmsAdminMessages['contentsection.configuration.workflows.task_details.assigned_to.dialog.title']}
<button aria-label="#{CmsAdminMessages['contentsection.configuration.workflow.task_details.assignments.dialog.close']}"
class="close"
data-dismiss="modal"
type="button">
<bootstrap:svgIcon icon="x" />
</button>
</h3>
</div>
<div class="modal-body">
<bootstrap:formGroupChecks
help="#{CmsAdminMessages['contentsection.configuration.workflow.task_details.assignments.dialog.roles.help']}"
inputId="assigments"
label="#{CmsAdminMessages['contentsection.configuration.workflow.task_details.assignments.dialog.roles.label']}"
name="assignments"
options="#{SelectedWorkflowTaskTemplateModel.availableRoles}"
selectedOptions="#{SelectedWorkflowTaskTemplateModel.assignedRoleKeys}"
/>
</div>
<div class="modal-footer">
<button class="btn btn-warning"
data-dismiss="modal"
type="button">
#{CmsAdminMessages['contentsection.configuration.workflow.task_details.assignments.dialog.close']}
</button>
<button class="btn btn-primary"
type="submit" >
#{CmsAdminMessages['contentsection.configuration.workflow.task_details.assignemnts.dialog.submit']}
</button>
</div>
</form>
</div>
</div>
<h2>#{CmsAdminMessages['contentsection.configuration.workflows.task_details.blocking_tasks.title']}</h2>
<div class="mb-2">
@ -139,7 +212,7 @@
id="add-blocking-task-dialog-title">
#{CmsAdminMessages['contentsection.configuration.workflows.task_details.blocking_tasks.add.dialog.title']}
</h3>
<button aria-label="#{CmsAdminMessages['contentsection.configuration.workflow.task_details.blocking_tasks.add.dialog.close']}"
<button aria-label="#{CmsAdminMessages['contentsection.configuration.workflow.task_details.blocking_tasks.add.dialog.close']}"
class="close"
data-dismiss="modal"
type="button">
@ -170,87 +243,87 @@
</div>
</div>
</div>
<table class="table table-hover workflow-tasks-table">
<thead>
<thead>
<tr>
<th scope="col">
#{CmsAdminMessages['contentsection.configuration.workflow.tasks.table.cols.label']}
</th>
<th colspan="2">
#{CmsAdminMessages['contentsection.configuration.workflow.tasks.table.cols.actions']}
</th>
</tr>
</thead>
<tbody>
<c:forEach items="#{SelectedWorkflowTaskTemplateModel.blockingTasks}"
var="task">
<tr>
<th scope="col">
#{CmsAdminMessages['contentsection.configuration.workflow.tasks.table.cols.label']}
</th>
<th colspan="2">
#{CmsAdminMessages['contentsection.configuration.workflow.tasks.table.cols.actions']}
</th>
</tr>
</thead>
<tbody>
<c:forEach items="#{SelectedWorkflowTaskTemplateModel.blockingTasks}"
var="task">
<tr>
<td>
<a href="#{mvc.basePath}/#{ContentSectionModel.sectionName}/configuration/workflows/UUID-#{SelectedWorkflowTemplateModel.uuid}/tasks/UUID-#{task.uuid}">
#{task.label}
</a>
</td>
<td class="info-col">
<button class="btn btn-info"
data-toggle="modal"
data-target="#task-#{task.uuid}-info-dialog"
type="button">
<bootstrap:svgIcon icon="info-circle" />
<span class="sr-only">
#{CmsAdminMessages['contentsection.configuration.workflow.tasks.table.info_button.label']}
</span>
</button>
<div aria-hidden="true"
aria-labelledby="task-#{task.uuid}-info-dialog-title"
class="modal fade"
id="task-#{task.uuid}-info-dialog"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title"
id="task-#{task.uuid}-info-dialog-title">
#{CmsAdminMessages.getMessage('contentsection.configuration.workflow.tasks.info.dialog.title', [ContentSectionModel.sectionName, SelectedWorkflowTemplateModel.displayName, task.label])}
</h2>
<button aria-label="#{CmsAdminMessages['contentsection.configuration.workflow.tasks.info.dialog.close']}"
class="close"
data-dismiss="modal"
type="button">
<bootstrap:svgIcon icon="x" />
</button>
</div>
<div class="modal-body">
<p>
#{task.description}
</p>
</div>
<div class="modal-footer">
<button class="btn btn-warning"
data-dismiss="modal"
type="button">
#{CmsAdminMessages['contentsection.configuration.workflow.tasks.info.dialog.close']}
</button>
</div>
<td>
<a href="#{mvc.basePath}/#{ContentSectionModel.sectionName}/configuration/workflows/UUID-#{SelectedWorkflowTemplateModel.uuid}/tasks/UUID-#{task.uuid}">
#{task.label}
</a>
</td>
<td class="info-col">
<button class="btn btn-info"
data-toggle="modal"
data-target="#task-#{task.uuid}-info-dialog"
type="button">
<bootstrap:svgIcon icon="info-circle" />
<span class="sr-only">
#{CmsAdminMessages['contentsection.configuration.workflow.tasks.table.info_button.label']}
</span>
</button>
<div aria-hidden="true"
aria-labelledby="task-#{task.uuid}-info-dialog-title"
class="modal fade"
id="task-#{task.uuid}-info-dialog"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title"
id="task-#{task.uuid}-info-dialog-title">
#{CmsAdminMessages.getMessage('contentsection.configuration.workflow.tasks.info.dialog.title', [ContentSectionModel.sectionName, SelectedWorkflowTemplateModel.displayName, task.label])}
</h2>
<button aria-label="#{CmsAdminMessages['contentsection.configuration.workflow.tasks.info.dialog.close']}"
class="close"
data-dismiss="modal"
type="button">
<bootstrap:svgIcon icon="x" />
</button>
</div>
<div class="modal-body">
<p>
#{task.description}
</p>
</div>
<div class="modal-footer">
<button class="btn btn-warning"
data-dismiss="modal"
type="button">
#{CmsAdminMessages['contentsection.configuration.workflow.tasks.info.dialog.close']}
</button>
</div>
</div>
</div>
</td>
<td class="del-col">
<libreccm:deleteDialog
actionTarget="#{mvc.basePath}/#{ContentSectionModel.sectionName}/configuration/workflows/UUID-#{SelectedWorkflowTemplateModel.uuid}/tasks/UUID-#{SelectedWorkflowTaskTemplateModel.uuid}/blockingTasks/#{task.uuid}/@remove"
buttonText="#{CmsAdminMessages['contentsection.configuration.workflow.tasks.remove.button.label']}"
cancelLabel="#{CmsAdminMessages['contentsection.configuration.workflow.tasks.remove.dialog.close']}"
confirmLabel="#{CmsAdminMessages['contentsection.configuration.workflow.tasks.remove.dialog.confirm']}"
dialogId="task-#{task.taskId}-remove-dialog"
dialogTitle="#{CmsAdminMessages['contentsection.configuration.workflow.tasks.remove.dialog.title']}"
message="#{CmsAdminMessages.getMessage('contentsection.configuration.workflow.tasks.remove.dialog.message', [ContentSectionModel.sectionName, SelectedWorkflowTemplateModel.displayName, task.label])}"
/>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</td>
<td class="del-col">
<libreccm:deleteDialog
actionTarget="#{mvc.basePath}/#{ContentSectionModel.sectionName}/configuration/workflows/UUID-#{SelectedWorkflowTemplateModel.uuid}/tasks/UUID-#{SelectedWorkflowTaskTemplateModel.uuid}/blockingTasks/#{task.uuid}/@remove"
buttonText="#{CmsAdminMessages['contentsection.configuration.workflow.tasks.remove.button.label']}"
cancelLabel="#{CmsAdminMessages['contentsection.configuration.workflow.tasks.remove.dialog.close']}"
confirmLabel="#{CmsAdminMessages['contentsection.configuration.workflow.tasks.remove.dialog.confirm']}"
dialogId="task-#{task.taskId}-remove-dialog"
dialogTitle="#{CmsAdminMessages['contentsection.configuration.workflow.tasks.remove.dialog.title']}"
message="#{CmsAdminMessages.getMessage('contentsection.configuration.workflow.tasks.remove.dialog.message', [ContentSectionModel.sectionName, SelectedWorkflowTemplateModel.displayName, task.label])}"
/>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</ui:define>

View File

@ -9,13 +9,13 @@
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link #{activeDocumentTab == 'editTab' ? 'active' : ''}"
href="#{mvc.basePath}/documents/#{CmsSelectedDocumentModel.itemPath}">
href="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}">
#{CmsAdminMessages['contentsection.document.tabs.edit.title']}
</a>
</li>
<li class="nav-item">
<a class="nav-link #{activeDocumentTab == 'history' ? 'active' : ''}"
href="#{mvc.basePath}/documents/#{CmsSelectedDocumentModel.itemPath}/@history">
href="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@history">
#{CmsAdminMessages['contentsection.document.tabs.history.title']}
</a>
</li>
@ -35,33 +35,44 @@
</button>
</p>
<p>
#{CmsAdminMessages['contentsection.document.authoring.workflow.active_task']}
<c:choose>
<c:when test="#{CmsSelectedDocumentModel.currentTask != null}">
#{CmsAdminMessages['contentsection.document.authoring.workflow.active_task']}
#{CmsSelectedDocumentModel.currentTask.label}
</c:when>
<c:otherwise>
#{CmsAdminMessages['contentsection.document.authoring.workflow.active_task.none']}
<form action="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@workflow/@start"
method="post">
<input name="returnUrl"
type="hidden"
value="#{CmsArticlePropertiesStep.stepPath}" />
<button class="btn btn-primary"
type="submit">
#{CmsAdminMessages['contentsection.document.authoring.workflow.start']}
</button>
</form>
</c:otherwise>
</c:choose>
<c:if test="#{CmsSelectedDocumentModel.currentTask != null}">
<c:choose>
<c:when test="#{CmsSelectedDocumentModel.currentTask.assignedToCurrentUser and CmsSelectedDocumentModel.currentTask.locked}">
<form action="#{mvc.basePath}/documents/#{CmsSelectedDocumentModel.itemPath}/@workflow/tasks/UUID-#{CmsSelectedDocumentModel.currentTask.taskUuid}/@unlock"
<form action="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@workflow/tasks/UUID-#{CmsSelectedDocumentModel.currentTask.taskUuid}/@unlock"
method="post">
<input name="returnUrl"
type="hidden"
value="#{mvc.basePath}/documents/#{CmsSelectedDocumentModel.itemPath}/@authoringsteps/#{authoringStep}" />
value="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@authoringsteps/#{authoringStep}" />
<button class="btn btn-secondary btn-sm"
type="submit">
#{CmsAdminMessages['contentsection.document.authoring.workflow.active_task.release']}
</button>
</form>
<form action="#{mvc.basePath}/documents/#{CmsSelectedDocumentModel.itemPath}/@workflow/tasks/UUID-#{CmsSelectedDocumentModel.currentTask.taskUuid}/@finish"
<form action="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@workflow/tasks/UUID-#{CmsSelectedDocumentModel.currentTask.taskUuid}/@finish"
method="post">
<input name="returnUrl"
type="hidden"
value="#{mvc.basePath}/documents/#{CmsSelectedDocumentModel.itemPath}/@authoringsteps/#{authoringStep}" />
value="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@authoringsteps/#{authoringStep}" />
<button class="btn btn-secondary btn-sm"
type="submit">
#{CmsAdminMessages['contentsection.document.authoring.workflow.active_task.finish']}
@ -69,11 +80,11 @@
</form>
</c:when>
<c:when test="#{CmsSelectedDocumentModel.currentTask.locked and CmsSelectedDocumentModel.canChangeWorkflow}">
<form action="#{mvc.basePath}/documents/#{CmsSelectedDocumentModel.itemPath}/@workflow/tasks/UUID-#{CmsSelectedDocumentModel.currentTask.taskUuid}/@lock"
<form action="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@workflow/tasks/UUID-#{CmsSelectedDocumentModel.currentTask.taskUuid}/@lock"
method="post">
<input name="returnUrl"
type="hidden"
value="#{mvc.basePath}/documents/#{CmsSelectedDocumentModel.itemPath}/@authoringsteps/#{authoringStep}" />
value="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@authoringsteps/#{authoringStep}" />
<button class="btn btn-secondary btn-sm"
type="submit">
#{CmsAdminMessages['contentsection.document.authoring.workflow.active_task.takeover']}
@ -81,10 +92,10 @@
</form>
</c:when>
<c:when test="#{CmsSelectedDocumentModel.currentTask.assignedToCurrentUser or CmsSelectedDocumentModel.canChangeWorkflow}">
<form action="#{mvc.basePath}/documents/#{CmsSelectedDocumentModel.itemPath}/@workflow/tasks/UUID-#{CmsSelectedDocumentModel.currentTask.taskUuid}/@lock">
<form action="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@workflow/tasks/UUID-#{CmsSelectedDocumentModel.currentTask.taskUuid}/@lock">
<input name="returnUrl"
type="hidden"
value="#{mvc.basePath}/documents/#{CmsSelectedDocumentModel.itemPath}/@authoringsteps/#{authoringStep}" />
value="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@authoringsteps/#{authoringStep}" />
<button class="btn btn-secondary btn-sm"
type="button">
#{CmsAdminMessages['contentsection.document.authoring.workflow.active_task.lock']}
@ -113,14 +124,14 @@
<li aria-current="#{'categorize' == authoringStep ? 'true' : ''}"
class="list-group-item #{'categorize' == authoringStep ? 'active' : ''}">
<a class="list-group-item-action"
href="#{mvc.basePath}/documents/#{CmsSelectedDocumentModel.itemPath}/@categorization">
href="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@categorization">
#{CmsAdminMessages['contentsection.document.authoring.steps.categorize.label']}
</a>
</li>
<li aria-current="#{'relatedInfo' == authoringStep ? 'true' : ''}"
class="list-group-item #{'relatedInfo' == authoringStep ? 'active' : ''}">
<a class="list-group-item-action"
href="#{mvc.basePath}/documents/#{CmsSelectedDocumentModel.itemPath}/@relatedinfo">
href="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@relatedinfo">
#{CmsAdminMessages['contentsection.document.authoring.steps.relatedInfo.label']}
</a>
</li>
@ -129,7 +140,7 @@
<li aria-current="#{'publish' == authoringStep ? 'true' : ''}"
class="list-group-item #{'publish' == authoringStep ? 'active' : ''}">
<a class="list-group-item-action"
href="#{mvc.basePath}/documents/#{CmsSelectedDocumentModel.itemPath}/@publish">
href="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@publish">
#{CmsAdminMessages['contentsection.document.authoring.steps.publish.label']}
</a>
</li>

View File

@ -12,61 +12,66 @@
<h3>#{CmsArticleMessageBundle.getMessage('basicproperties.name.header')}</h3>
<div class="d-flex">
<pre class="mr-2">#{CmsArticlePropertiesStep.name}</pre>
<button class="btn btn-primary btn-sm"
data-toggle="modal"
data-target="#name-edit-dialog"
type="button">
<bootstrap:svgIcon icon="pen" />
<span class="sr-only">
#{CmsArticleMessageBundle['basicproperties.name.edit']}
</span>
</button>
<c:if test="#{CmsArticlePropertiesStep.canEdit}">
<button class="btn btn-primary btn-sm"
data-toggle="modal"
data-target="#name-edit-dialog"
type="button">
<bootstrap:svgIcon icon="pen" />
<span class="sr-only">
#{CmsArticleMessageBundle['basicproperties.name.edit']}
</span>
</button>
</c:if>
</div>
<div aria-hidden="true"
aria-labelledby="name-edit-dialog-title"
class="modal fade"
id="name-edit-dialog"
tabindex="-1">
<div class="modal-dialog">
<form action="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@article-basicproperties/name"
class="modal-content"
method="post">
<div class="modal-header">
<h4 class="modal-title"
id="name-edit-dialog-title">
#{CmsArticleMessageBundle['basicproperties.name.edit.title']}
</h4>
<button aria-label="#{CmsArticleMessageBundle['basicproperties.name.edit.close']}"
class="close"
data-dismiss="modal"
type="button">
<bootstrap:svgIcon icon="x-circle" />
</button>
</div>
<div class="modal-body">
<bootstrap:formGroupText
help="#{CmsArticleMessageBundle['basicproperties.name.help']}"
inputId="name"
label="#{CmsArticleMessageBundle['basicproperties.name.label']}"
name="name"
pattern="^([a-zA-Z0-9_-]*)$"
required="true"
value="#{CmsArticlePropertiesStep.name}"/>
</div>
<div class="modal-footer">
<button class="btn btn-warning"
data-dismiss="modal"
type="button">
#{CmsArticleMessageBundle['basicproperties.name.edit.close']}
</button>
<button class="btn btn-success"
type="submit">
#{CmsArticleMessageBundle['basicproperties.name.edit.submit']}
</button>
</div>
</form>
<c:if test="#{CmsArticlePropertiesStep.canEdit}">
<div aria-hidden="true"
aria-labelledby="name-edit-dialog-title"
class="modal fade"
id="name-edit-dialog"
tabindex="-1">
<div class="modal-dialog">
<form action="#{mvc.basePath}/#{ContentSectionModel.sectionName}/documents/#{CmsSelectedDocumentModel.itemPath}/@article-basicproperties/name"
class="modal-content"
method="post">
<div class="modal-header">
<h4 class="modal-title"
id="name-edit-dialog-title">
#{CmsArticleMessageBundle['basicproperties.name.edit.title']}
</h4>
<button aria-label="#{CmsArticleMessageBundle['basicproperties.name.edit.close']}"
class="close"
data-dismiss="modal"
type="button">
<bootstrap:svgIcon icon="x-circle" />
</button>
</div>
<div class="modal-body">
<bootstrap:formGroupText
help="#{CmsArticleMessageBundle['basicproperties.name.help']}"
inputId="name"
label="#{CmsArticleMessageBundle['basicproperties.name.label']}"
name="name"
pattern="^([a-zA-Z0-9_-]*)$"
required="true"
value="#{CmsArticlePropertiesStep.name}"/>
</div>
<div class="modal-footer">
<button class="btn btn-warning"
data-dismiss="modal"
type="button">
#{CmsArticleMessageBundle['basicproperties.name.edit.close']}
</button>
<button class="btn btn-success"
type="submit">
#{CmsArticleMessageBundle['basicproperties.name.edit.submit']}
</button>
</div>
</form>
</div>
</div>
</div>
</c:if>
<libreccm:localizedStringEditor
@ -90,6 +95,7 @@
hasUnusedLocales="#{!CmsArticlePropertiesStep.unusedTitleLocales.isEmpty()}"
headingLevel="3"
objectIdentifier="#{CmsSelectedDocumentModel.itemPath}"
readOnly="#{!CmsArticlePropertiesStep.canEdit}"
removeButtonLabel="#{CmsArticleMessageBundle['basicproperties.title.remove']}"
removeDialogCancelLabel="#{CmsArticleMessageBundle['basicproperties.title.remove.cancel']}"
removeDialogSubmitLabel="#{CmsArticleMessageBundle['basicproperties.title.remove.submit']}"
@ -123,6 +129,7 @@
hasUnusedLocales="#{!CmsArticlePropertiesStep.unusedDescriptionLocales.isEmpty()}"
headingLevel="3"
objectIdentifier="#{CmsSelectedDocumentModel.itemPath}"
readOnly="#{!CmsArticlePropertiesStep.canEdit}"
removeButtonLabel="#{CmsArticleMessageBundle['basicproperties.description.remove']}"
removeDialogCancelLabel="#{CmsArticleMessageBundle['basicproperties.description.remove.cancel']}"
removeDialogSubmitLabel="#{CmsArticleMessageBundle['basicproperties.description.remove.submit']}"

View File

@ -756,3 +756,13 @@ contentsection.documentfolder.new_document_dialog.close=Cancel
contentsection.documentfolder.new_document_dialog.documenttype.help=The type of the new document
contentsection.documentfolder.new_document_dialog.documenttype.label=Document Type
contentsection.documentfolder.new_document_dialog.submit=Create document
contentsection.document.authoring.workflow.start=Start workflow
ontentsection.configuration.workflows.task_details.assigned_to.title=Assigned to
contentsection.configuration.workflows.task_details.assigned_to.edit=Edit assignments
contentsection.configuration.workflows.task_details.not_assigned=This task is not assigned to any roles.
contentsection.configuration.workflow.task_details.assignments.dialog.close=Cancel
contentsection.configuration.workflow.task_details.assignemnts.dialog.submit=Update assigned roles
contentsection.configuration.workflows.task_details.assigned_to.title=Assigned roles
contentsection.configuration.workflow.task_details.assignments.dialog.roles.label=Roles
contentsection.configuration.workflow.task_details.assignments.dialog.roles.help=Select the roles to which task is assigned.
contentsection.configuration.workflows.task_details.assigned_to.dialog.title=Edit assigned roles

View File

@ -757,3 +757,13 @@ contentsection.documentfolder.new_document_dialog.close=Abbrechen
contentsection.documentfolder.new_document_dialog.documenttype.help=Der Typ des neuen Dokumentes
contentsection.documentfolder.new_document_dialog.documenttype.label=Dokument Typ
contentsection.documentfolder.new_document_dialog.submit=Dokument anlegen
contentsection.document.authoring.workflow.start=Arbeitsablauf starten
ontentsection.configuration.workflows.task_details.assigned_to.title=Zugewiesen an
contentsection.configuration.workflows.task_details.assigned_to.edit=Zuweisungen bearbeiten
contentsection.configuration.workflows.task_details.not_assigned=Diese Aufgabe ist keiner Rolle zugewiesen
contentsection.configuration.workflow.task_details.assignments.dialog.close=Abbrechen
contentsection.configuration.workflow.task_details.assignemnts.dialog.submit=Zugewiesene Rollen aktualisieren
contentsection.configuration.workflows.task_details.assigned_to.title=Zugewiesen Rollen
contentsection.configuration.workflow.task_details.assignments.dialog.roles.label=Rollen
contentsection.configuration.workflow.task_details.assignments.dialog.roles.help=W\u00e4hlen Sie die Rollen, denen die Aufgabe zugewiesen ist.
contentsection.configuration.workflows.task_details.assigned_to.dialog.title=Zugewiesene Rollen bearbeiten

View File

@ -26,8 +26,10 @@ import org.libreccm.security.User;
import javax.enterprise.context.RequestScoped;
import javax.persistence.NoResultException;
import javax.persistence.TypedQuery;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
/**
@ -60,6 +62,13 @@ public class AssignableTaskRepository
public boolean isNew(final AssignableTask task) {
return task.getTaskId() == 0;
}
@Override
protected void initNewEntity(final AssignableTask entity) {
if (isNew(entity)) {
entity.setUuid(UUID.randomUUID().toString());
}
}
/**
* Finds a {@link AssignableTask} by its uuid.
@ -110,4 +119,6 @@ public class AssignableTaskRepository
return query.getResultList();
}
}

View File

@ -162,6 +162,10 @@ public class WorkflowManager implements Serializable {
tasks);
});
for(final Map.Entry<Long, Task> task : tasks.entrySet()) {
task.getValue().setTaskState(TaskState.DISABLED);
}
workflow.setObject(object);
workflow.setState(WorkflowState.INIT);

View File

@ -102,6 +102,11 @@
required="true"
shortDescription="Identifier of the object to which the localized string belongs"
type="String" />
<cc:attribute name="readOnly"
default="false"
required="false"
shortDescription="Don't show edit buttons."
type="boolean" />
<cc:attribute name="removeButtonLabel"
default="Remove"
required="false"
@ -190,7 +195,7 @@
<div>#{cc.attrs.title}</div>
</c:otherwise>
</c:choose>
<c:if test="#{cc.attrs.hasUnusedLocales}">
<c:if test="#{cc.attrs.hasUnusedLocales and !cc.attrs.readOnly}">
<div class="mb-2">
<div class="text-right">
<button class="btn btn-secondary"
@ -331,174 +336,178 @@
<td>#{entry.key}</td>
<td>#{entry.value}</td>
<td>
<div class="text-center">
<button class="btn btn-info"
data-target="##{cc.attrs.editorId}-#{entry.key}-edit-dialog"
data-toggle="modal"
type="button">
<bootstrap:svgIcon icon="pen" />
<span>#{cc.attrs.editButtonLabel}</span>
</button>
</div>
<div aria-labelledby="#{cc.attrs.editorId}-#{entry.key}-edit-dialog-title"
aria-hidden="true"
class="modal fade"
data-backdrop="static"
id="#{cc.attrs.editorId}-#{entry.key}-edit-dialog"
tabindex="-1">
<div class="modal-dialog">
<form accept-charset="UTF-8"
action="#{cc.attrs.editMethod}/#{entry.key}"
class="modal-content"
method="post">
<div class="modal-header">
<c:choose>
<c:when test="#{cc.attrs.headingLevel == 1}">
<h2>#{cc.attrs.editDialogTitle}</h2>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 2}">
<h3>#{cc.attrs.editDialogTitle}</h3>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 3}">
<h4>#{cc.attrs.editDialogTitle}</h4>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 4}">
<h5>#{cc.attrs.editDialogTitle}</h5>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 5}">
<h6>#{cc.attrs.editDialogTitle}</h6>
</c:when>
<c:otherwise>
<div>#{cc.attrs.editDialogTitle}</div>
</c:otherwise>
</c:choose>
<button aria-label="#{cc.attrs.editDialogCancelLabel}"
class="close"
data-dismiss="modal"
type="button" >
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<input name="locale"
value="#{entry.key}"
type="hidden" />
<div class="form-group">
<label for="#{cc.attrs.editorId}-#{entry.key}-edit-dialog-value}">
#{cc.attrs.editDialogValueLabel}
</label>
<c:if test="#{!cc.attrs.readOnly}">
<div class="text-center">
<button class="btn btn-info"
data-target="##{cc.attrs.editorId}-#{entry.key}-edit-dialog"
data-toggle="modal"
type="button">
<bootstrap:svgIcon icon="pen" />
<span>#{cc.attrs.editButtonLabel}</span>
</button>
</div>
<div aria-labelledby="#{cc.attrs.editorId}-#{entry.key}-edit-dialog-title"
aria-hidden="true"
class="modal fade"
data-backdrop="static"
id="#{cc.attrs.editorId}-#{entry.key}-edit-dialog"
tabindex="-1">
<div class="modal-dialog">
<form accept-charset="UTF-8"
action="#{cc.attrs.editMethod}/#{entry.key}"
class="modal-content"
method="post">
<div class="modal-header">
<c:choose>
<c:when test="#{cc.attrs.useTextarea}">
<textarea aria-describedby="#{cc.attrs.editorId}-#{entry.key}-edit-dialog-value-help"
class="form-control"
cols="80"
id="#{cc.attrs.editorId}-#{entry.key}-edit-dialog-value"
name="value"
required="true"
rows="10">#{entry.value}</textarea>
<c:when test="#{cc.attrs.headingLevel == 1}">
<h2>#{cc.attrs.editDialogTitle}</h2>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 2}">
<h3>#{cc.attrs.editDialogTitle}</h3>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 3}">
<h4>#{cc.attrs.editDialogTitle}</h4>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 4}">
<h5>#{cc.attrs.editDialogTitle}</h5>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 5}">
<h6>#{cc.attrs.editDialogTitle}</h6>
</c:when>
<c:otherwise>
<input aria-describedby="#{cc.attrs.editorId}-#{entry.key}-edit-dialog-value-help"
class="form-control"
id="#{cc.attrs.editorId}-#{entry.key}-edit-dialog-value"
name="value"
required="true"
type="text"
value="#{entry.value}" />
<div>#{cc.attrs.editDialogTitle}</div>
</c:otherwise>
</c:choose>
<small class="form-text text-muted"
id="#{cc.attrs.editorId}-#{entry.key}-edit-dialog-value-help">
#{cc.attrs.editDialogValueHelp}
</small>
<button aria-label="#{cc.attrs.editDialogCancelLabel}"
class="close"
data-dismiss="modal"
type="button" >
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<input name="locale"
value="#{entry.key}"
type="hidden" />
<div class="form-group">
<label for="#{cc.attrs.editorId}-#{entry.key}-edit-dialog-value}">
#{cc.attrs.editDialogValueLabel}
</label>
<c:choose>
<c:when test="#{cc.attrs.useTextarea}">
<textarea aria-describedby="#{cc.attrs.editorId}-#{entry.key}-edit-dialog-value-help"
class="form-control"
cols="80"
id="#{cc.attrs.editorId}-#{entry.key}-edit-dialog-value"
name="value"
required="true"
rows="10">#{entry.value}</textarea>
</c:when>
<c:otherwise>
<input aria-describedby="#{cc.attrs.editorId}-#{entry.key}-edit-dialog-value-help"
class="form-control"
id="#{cc.attrs.editorId}-#{entry.key}-edit-dialog-value"
name="value"
required="true"
type="text"
value="#{entry.value}" />
</c:otherwise>
</c:choose>
<small class="form-text text-muted"
id="#{cc.attrs.editorId}-#{entry.key}-edit-dialog-value-help">
#{cc.attrs.editDialogValueHelp}
</small>
</div>
</div>
<div class="modal-footer">
<div class="modal-footer">
<button class="btn btn-secondary"
data-dismiss="modal"
type="button" >
#{cc.attrs.editDialogCancelLabel}
</button>
<button type="submit" class="btn btn-primary">
#{cc.attrs.editDialogSubmitLabel}
</button>
</div>
</div>
</form>
</div>
</div>
</c:if>
</td>
<td>
<c:if test="#{!cc.attrs.readOnly}">
<div class="text-center">
<button class="btn btn-danger"
data-target="##{cc.attrs.editorId}-#{entry.key}-remove-dialog"
data-toggle="modal"
type="button">
<bootstrap:svgIcon icon="x-circle" />
<span>#{cc.attrs.removeButtonLabel}</span>
</button>
</div>
<div aria-describedby="#{cc.attrs.editorId}-#{entry.key}-remove-dialog-title"
aria-hidden="true"
class="modal fade"
data-backdrop="static"
id="#{cc.attrs.editorId}-#{entry.key}-remove-dialog"
tabindex="-1">
<div class="modal-dialog">
<form action="#{cc.attrs.removeMethod}/#{entry.key}"
class="modal-content"
method="post">
<div class="modal-header">
<c:choose>
<c:when test="#{cc.attrs.headingLevel == 1}">
<h2>#{cc.attrs.removeDialogTitle}</h2>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 2}">
<h3>#{cc.attrs.removeDialogTitle}</h3>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 3}">
<h4>#{cc.attrs.removeDialogTitle}</h4>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 4}">
<h5>#{cc.attrs.removeDialogTitle}</h5>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 5}">
<h6>#{cc.attrs.removeDialogTitle}</h6>
</c:when>
<c:otherwise>
<div>#{cc.attrs.removeDialogTitle}</div>
</c:otherwise>
</c:choose>
<button aria-label="#{cc.attrs.removeDialogCancelLabel}"
class="close"
data-dismiss="modal"
type="button" >
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>
#{cc.attrs.removeDialogText}
</p>
<pre>#{entry.key}: #{entry.value}</pre>
<input name="confirmed"
type="hidden"
value="true" />
</div>
</div>
<div class="modal-footer">
<div class="modal-footer">
<button class="btn btn-secondary"
data-dismiss="modal"
type="button" >
#{cc.attrs.editDialogCancelLabel}
#{cc.attrs.removeDialogCancelLabel}
</button>
<button type="submit" class="btn btn-primary">
#{cc.attrs.editDialogSubmitLabel}
<button type="submit" class="btn btn-danger">
#{cc.attrs.removeDialogSubmitLabel}
</button>
</div>
</div>
</form>
</form>
</div>
</div>
</div>
</td>
<td>
<div class="text-center">
<button class="btn btn-danger"
data-target="##{cc.attrs.editorId}-#{entry.key}-remove-dialog"
data-toggle="modal"
type="button">
<bootstrap:svgIcon icon="x-circle" />
<span>#{cc.attrs.removeButtonLabel}</span>
</button>
</div>
<div aria-describedby="#{cc.attrs.editorId}-#{entry.key}-remove-dialog-title"
aria-hidden="true"
class="modal fade"
data-backdrop="static"
id="#{cc.attrs.editorId}-#{entry.key}-remove-dialog"
tabindex="-1">
<div class="modal-dialog">
<form action="#{cc.attrs.removeMethod}/#{entry.key}"
class="modal-content"
method="post">
<div class="modal-header">
<c:choose>
<c:when test="#{cc.attrs.headingLevel == 1}">
<h2>#{cc.attrs.removeDialogTitle}</h2>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 2}">
<h3>#{cc.attrs.removeDialogTitle}</h3>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 3}">
<h4>#{cc.attrs.removeDialogTitle}</h4>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 4}">
<h5>#{cc.attrs.removeDialogTitle}</h5>
</c:when>
<c:when test="#{cc.attrs.headingLevel == 5}">
<h6>#{cc.attrs.removeDialogTitle}</h6>
</c:when>
<c:otherwise>
<div>#{cc.attrs.removeDialogTitle}</div>
</c:otherwise>
</c:choose>
<button aria-label="#{cc.attrs.removeDialogCancelLabel}"
class="close"
data-dismiss="modal"
type="button" >
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>
#{cc.attrs.removeDialogText}
</p>
<pre>#{entry.key}: #{entry.value}</pre>
<input name="confirmed"
type="hidden"
value="true" />
</div>
<div class="modal-footer">
<button class="btn btn-secondary"
data-dismiss="modal"
type="button" >
#{cc.attrs.removeDialogCancelLabel}
</button>
<button type="submit" class="btn btn-danger">
#{cc.attrs.removeDialogSubmitLabel}
</button>
</div>
</form>
</div>
</div>
</c:if>
</td>
</tr>
</c:forEach>