diff --git a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/persistence/AuthorityDAO.java b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/persistence/AuthorityDAO.java index f58dd6674cf..3ba65b51a53 100644 --- a/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/persistence/AuthorityDAO.java +++ b/Kitodo-DataManagement/src/main/java/org/kitodo/data/database/persistence/AuthorityDAO.java @@ -61,6 +61,6 @@ public Authority getByTitle(String title) throws DAOException { if (!authorities.isEmpty()) { return authorities.get(0); } - throw new DAOException("Object cannot be found in database"); + throw new DAOException(String.format("Authority '%s' cannot be found in database", title)); } } diff --git a/Kitodo-DataManagement/src/main/resources/db/migration/V2_131__Add_permission_to_link_to_processes_of_unassigend_projects.sql b/Kitodo-DataManagement/src/main/resources/db/migration/V2_131__Add_permission_to_link_to_processes_of_unassigend_projects.sql new file mode 100644 index 00000000000..7104aaf1809 --- /dev/null +++ b/Kitodo-DataManagement/src/main/resources/db/migration/V2_131__Add_permission_to_link_to_processes_of_unassigend_projects.sql @@ -0,0 +1,17 @@ +-- +-- (c) Kitodo. Key to digital objects e. V. +-- +-- This file is part of the Kitodo project. +-- +-- It is licensed under GNU General Public License version 3 or later. +-- +-- For the full copyright and license information, please read the +-- GPL3-License.txt file that was distributed with this source code. +-- + +SET SQL_SAFE_UPDATES = 0; + +-- add authorities/permission for linking to parent processes of unassigned projects +INSERT IGNORE INTO authority (title) VALUES ('linkToProcessesOfUnassignedProjects_clientAssignable'); + +SET SQL_SAFE_UPDATES = 1; diff --git a/Kitodo/src/main/java/org/kitodo/production/controller/SecurityAccessController.java b/Kitodo/src/main/java/org/kitodo/production/controller/SecurityAccessController.java index b48aa2bef9f..bdcbe2e794f 100644 --- a/Kitodo/src/main/java/org/kitodo/production/controller/SecurityAccessController.java +++ b/Kitodo/src/main/java/org/kitodo/production/controller/SecurityAccessController.java @@ -1108,4 +1108,13 @@ public boolean hasAuthorityToRenameMediaFiles() { public boolean hasAuthorityToAssignImportConfigurationToClient() { return securityAccessService.hasAuthorityToAssignImportConfigurationToClient(); } + + /** + * Check if the current user has the permission to link processes to parent processes of unassigned projects. + * + * @return true if the current user has the permission to link processes to parent processes of unassigned projects. + */ + public boolean hasAuthorityToLinkToProcessesOfUnassignedProjects() { + return securityAccessService.hasAuthorityToLinkToProcessesOfUnassignedProjects(); + } } diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/ProcessForm.java b/Kitodo/src/main/java/org/kitodo/production/forms/ProcessForm.java index 3d14dfa4f8a..2f972b318fd 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/ProcessForm.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/ProcessForm.java @@ -18,7 +18,9 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import javax.annotation.PostConstruct; @@ -60,6 +62,7 @@ import org.kitodo.production.process.ProcessValidator; import org.kitodo.production.services.ServiceManager; import org.kitodo.production.services.command.KitodoScriptService; +import org.kitodo.production.services.data.ImportService; import org.kitodo.production.services.data.ProcessService; import org.kitodo.production.services.file.FileService; import org.kitodo.production.services.workflow.WorkflowControllerService; @@ -96,6 +99,7 @@ public class ProcessForm extends TemplateBaseForm { private static final String CREATE_PROCESS_PATH = "/pages/processFromTemplate.jsf?faces-redirect=true"; private static final String PROCESS_TABLE_VIEW_ID = "/pages/processes.xhtml"; private static final String PROCESS_TABLE_ID = "processesTabView:processesForm:processesTable"; + private final Map assignedProcesses = new HashMap<>(); @Inject private CustomListColumnInitializer initializer; @@ -1169,4 +1173,21 @@ public String getMediaRenamingConfirmMessage() { public String getErrorMessage() { return errorMessage; } + + /** + * Check and return whether process with ID 'processId' belongs to a project assigned to the current user or not. + * @param processId ID of process to check + * @return whether process belongs to project assigned to current user or not + */ + public boolean processInAssignedProject(int processId) { + try { + if (!assignedProcesses.containsKey(processId)) { + assignedProcesses.put(processId, ImportService.processInAssignedProject(processId)); + } + return assignedProcesses.get(processId); + } catch (DAOException e) { + Helper.setErrorMessage(e); + } + return false; + } } diff --git a/Kitodo/src/main/java/org/kitodo/production/forms/createprocess/TitleRecordLinkTab.java b/Kitodo/src/main/java/org/kitodo/production/forms/createprocess/TitleRecordLinkTab.java index ac258eebefe..0224b4f742d 100644 --- a/Kitodo/src/main/java/org/kitodo/production/forms/createprocess/TitleRecordLinkTab.java +++ b/Kitodo/src/main/java/org/kitodo/production/forms/createprocess/TitleRecordLinkTab.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -23,6 +24,7 @@ import java.util.Objects; import java.util.Optional; +import javax.faces.context.FacesContext; import javax.faces.model.SelectItem; import org.apache.commons.lang3.StringUtils; @@ -40,6 +42,7 @@ import org.kitodo.production.helper.Helper; import org.kitodo.production.metadata.MetadataEditor; import org.kitodo.production.services.ServiceManager; +import org.kitodo.production.services.data.ImportService; import org.kitodo.production.services.data.ProcessService; import org.kitodo.production.services.dataformat.MetsService; import org.omnifaces.util.Ajax; @@ -53,6 +56,7 @@ public class TitleRecordLinkTab { private static final Logger logger = LogManager.getLogger(TitleRecordLinkTab.class); static final String INSERTION_TREE = "editForm:processFromTemplateTabView:insertionTree"; + static final String PARENT_PROCESS_SELECTION = "editForm:processFromTemplateTabView:chooseParentGroup"; private static final MetsService metsService = ServiceManager.getMetsService(); private static final ProcessService processService = ServiceManager.getProcessService(); @@ -160,12 +164,12 @@ public void createInsertionPositionSelectionTree() throws DAOException, DataExce priorityList); logicalStructure.setExpanded(true); - if (!selectableInsertionPositions.isEmpty()) { - selectedInsertionPosition = (String) ((LinkedList) selectableInsertionPositions).getLast() - .getValue(); - } else { + if (selectableInsertionPositions.isEmpty()) { selectedInsertionPosition = null; Helper.setErrorMessage("createProcessForm.titleRecordLinkTab.noInsertionPosition"); + } else { + selectedInsertionPosition = (String) ((LinkedList) selectableInsertionPositions).getLast() + .getValue(); } } @@ -308,21 +312,31 @@ public void searchForParentProcesses() { } try { List processes = ServiceManager.getProcessService().findLinkableParentProcesses(searchQuery, - createProcessForm.getProject().getId(), createProcessForm.getTemplate().getRuleset().getId()); + createProcessForm.getTemplate().getRuleset().getId()); if (processes.isEmpty()) { Helper.setMessage("createProcessForm.titleRecordLinkTab.searchButtonClick.noHits"); } indicationOfMoreHitsVisible = processes.size() > MAXIMUM_NUMBER_OF_HITS; - possibleParentProcesses = new ArrayList<>(); - for (ProcessDTO process : processes.subList(0, Math.min(processes.size(), MAXIMUM_NUMBER_OF_HITS))) { - possibleParentProcesses.add(new SelectItem(process.getId().toString(), process.getTitle())); - } - } catch (DataException e) { + possibleParentProcesses = ServiceManager.getImportService() + .getPotentialParentProcesses(processes, MAXIMUM_NUMBER_OF_HITS); + } catch (DataException | DAOException | IOException e) { Helper.setErrorMessage("createProcessForm.titleRecordLinkTab.searchButtonClick.error", e.getMessage(), - logger, e); + logger, e); indicationOfMoreHitsVisible = false; possibleParentProcesses = Collections.emptyList(); } + possibleParentProcesses.sort(Comparator.comparing(SelectItem::getLabel)); + for (SelectItem selectItem : possibleParentProcesses) { + if (!selectItem.isDisabled()) { + try { + int processId = Integer.parseInt(selectItem.getValue().toString()); + setParentAsTitleRecord(ServiceManager.getProcessService().getById(processId)); + break; + } catch (DAOException | NumberFormatException e) { + logger.error(e); + } + } + } } /** @@ -429,15 +443,25 @@ public void setTitleRecordProcess(Process titleRecordProcess) { /** * Set given process "parentProcess" as parent title record of new process. + * * @param parentProcess process to set as parent title record */ public void setParentAsTitleRecord(Process parentProcess) { createProcessForm.setEditActiveTabIndex(CreateProcessForm.TITLE_RECORD_LINK_TAB_INDEX); - ArrayList parentCandidates = new ArrayList<>(); - parentCandidates.add(new SelectItem(parentProcess.getId().toString(), parentProcess.getTitle())); - createProcessForm.getTitleRecordLinkTab().setPossibleParentProcesses(parentCandidates); - createProcessForm.getTitleRecordLinkTab().setChosenParentProcess((String)parentCandidates.get(0).getValue()); - createProcessForm.getTitleRecordLinkTab().chooseParentProcess(); - Ajax.update(INSERTION_TREE); + try { + if (ImportService.userMayLinkToParent(parentProcess.getId())) { + setChosenParentProcess(String.valueOf(parentProcess.getId())); + } else { + setChosenParentProcess(null); + } + } catch (DAOException e) { + Helper.setErrorMessage(e); + } + chooseParentProcess(); + // only update UI components if FacesContext exists (not the case during integration tests, for example) + if (Objects.nonNull(FacesContext.getCurrentInstance())) { + Ajax.update(PARENT_PROCESS_SELECTION); + Ajax.update(INSERTION_TREE); + } } } diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/ImportService.java b/Kitodo/src/main/java/org/kitodo/production/services/data/ImportService.java index 0bb9a2b95c1..db11aab30de 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/ImportService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/ImportService.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; @@ -32,6 +33,7 @@ import java.util.Objects; import java.util.stream.Collectors; +import javax.faces.model.SelectItem; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.xpath.XPath; @@ -956,14 +958,14 @@ private Process loadParentProcess(Ruleset ruleset, int projectId, String parentI HashMap parentIDMetadata = new HashMap<>(); parentIDMetadata.put(identifierMetadata, parentId); try { - for (ProcessDTO processDTO : ServiceManager.getProcessService().findByMetadata(parentIDMetadata, true)) { + for (ProcessDTO processDTO : sortProcessesByProjectID(ServiceManager.getProcessService() + .findByMetadataInAllProjects(parentIDMetadata, true), projectId)) { Process process = ServiceManager.getProcessService().getById(processDTO.getId()); if (Objects.isNull(process.getRuleset()) || Objects.isNull(process.getRuleset().getId())) { throw new ProcessGenerationException("Ruleset or ruleset ID of potential parent process " + process.getId() + " is null!"); } - if (process.getProject().getId() == projectId - && process.getRuleset().getId().equals(ruleset.getId())) { + if (process.getRuleset().getId().equals(ruleset.getId())) { parentProcess = process; break; } @@ -976,6 +978,25 @@ private Process loadParentProcess(Ruleset ruleset, int projectId, String parentI return parentProcess; } + /** + * Sorts a list of process DTOs based on a provided projectId. + * Processes which match the provided projectId should come first. + * @param processDTOs list of process DTOs + * @param projectId projectId by which the list gets sorted + */ + public List sortProcessesByProjectID(List processDTOs, int projectId) { + List sortedList = new ArrayList<>(processDTOs); + Comparator comparator = Comparator.comparingInt(obj -> { + if (obj.getProject().getId() == projectId) { + return 0; // Matching value should come first + } else { + return 1; // Non-matching value comes later + } + }); + sortedList.sort(comparator); + return sortedList; + } + /** * Check and return whether the "parentIdSearchField" is configured in the current ImportConfiguration. * @@ -1396,4 +1417,63 @@ public boolean isRecordIdentifierMetadataConfigured(RulesetManagementInterface r public Collection getDetailsOfRecordIdentifierMissingError() { return recordIdentifierMissingDetails; } + + /** + * Create and return list of "SelectItem" objects for given list of "ProcessDTO"s. For each "ProcessDTO" object + * check whether current user can link to it as a parent process. + * - If a specific process belongs to a project that is not assigned to the current user, add a hint to message to + * the corresponding "SelectItem" + * - If the user cannot link to a specific process, because the process belongs to a project which is not assigned + * to him, and he also lacks the special permission to link to processes in unassigned projects, the corresponding + * "SelectItem" is disabled. + * @param parentCandidates list of "ProcessDTO"s + * @param maxNumber limit + * @return list of "SelectItem" objects corresponding to given "ProcessDTO" objects. + * @throws DAOException when checking whether user can link to given "ProcessDTO"s fails + */ + public ArrayList getPotentialParentProcesses(List parentCandidates, int maxNumber) + throws DAOException { + ArrayList possibleParentProcesses = new ArrayList<>(); + for (ProcessDTO process : parentCandidates.subList(0, Math.min(parentCandidates.size(), maxNumber))) { + SelectItem selectItem = new SelectItem(process.getId().toString(), process.getTitle()); + selectItem.setDisabled(!userMayLinkToParent(process.getId())); + if (!processInAssignedProject(process.getId())) { + String problem = Helper.getTranslation("projectNotAssignedToCurrentUser", process.getProject() + .getTitle()); + selectItem.setDescription(problem); + selectItem.setLabel(selectItem.getLabel() + " (" + problem + ")"); + } + possibleParentProcesses.add(selectItem); + } + return possibleParentProcesses; + } + + /** + * Check and return whether the process with the provided ID "processId" belongs to a project that is assigned to + * the current user or not. + * @param processId ID of the process to check + * @return whether the process with the provided ID belongs to a project assigned to the current user or not + * @throws DAOException when retrieving the process with the ID "processId" from the database fails + */ + public static boolean processInAssignedProject(int processId) throws DAOException { + Process process = ServiceManager.getProcessService().getById(processId); + if (Objects.nonNull(process)) { + return ServiceManager.getUserService().getCurrentUser().getProjects().contains(process.getProject()); + } + return false; + } + + /** + * Check and return whether current user is allowed to link to process with provided ID "processId". For this + * method to return "true", one of the following two conditions has to be met: + * 1. the project of the process with the provided ID is assigned to the current user OR + * 2. the current user has the special permission to link to parent processes of unassigned projects + * @param processId the ID of the process to which a link is to be established + * @return whether the current user can link to the process with the provided ID or not + * @throws DAOException when checking whether the process with provided ID fails + */ + public static boolean userMayLinkToParent(int processId) throws DAOException { + return processInAssignedProject(processId) + || ServiceManager.getSecurityAccessService().hasAuthorityToLinkToProcessesOfUnassignedProjects(); + } } diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/ProcessService.java b/Kitodo/src/main/java/org/kitodo/production/services/data/ProcessService.java index 25a3abcf045..b813448d4cb 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/ProcessService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/ProcessService.java @@ -607,6 +607,23 @@ public List findByMetadata(Map metadata) throws Data * @return list of ProcessDTO objects with processes for specific metadata tag */ public List findByMetadata(Map metadata, boolean exactMatch) throws DataException { + QueryBuilder query = constructMetadataQuery(metadata, exactMatch); + return findByQuery(nestedQuery(METADATA_SEARCH_KEY, query, ScoreMode.Total), true); + } + + /** + * Find processes by metadata across all projects of the current client. + * + * @param metadata + * key is metadata tag and value is metadata content + * @return list of ProcessDTO objects with processes for specific metadata tag + */ + public List findByMetadataInAllProjects(Map metadata, boolean exactMatch) throws DataException { + QueryBuilder query = constructMetadataQuery(metadata, exactMatch); + return findByQueryInAllProjects(nestedQuery(METADATA_SEARCH_KEY, query, ScoreMode.Total), true); + } + + private BoolQueryBuilder constructMetadataQuery(Map metadata, boolean exactMatch) { String nameSearchKey = METADATA_SEARCH_KEY + ".name"; String contentSearchKey = METADATA_SEARCH_KEY + ".content"; if (exactMatch) { @@ -620,8 +637,7 @@ public List findByMetadata(Map metadata, boolean exa pairQuery.must(matchQuery(contentSearchKey, entry.getValue())); query.must(pairQuery); } - - return findByQuery(nestedQuery(METADATA_SEARCH_KEY, query, ScoreMode.Total), true); + return query; } /** @@ -799,16 +815,15 @@ public List findLinkableChildProcesses(String searchInput, int rules * * @param searchInput * user input - * @param projectId - * the id of the allowed project * @param rulesetId * the id of the allowed ruleset * @return found processes - * @throws DataException - * if the search engine fails + * @throws DataException if the search engine fails + * @throws DAOException when loading ruleset from database fails + * @throws IOException when opening ruleset file fails */ - public List findLinkableParentProcesses(String searchInput, int projectId, int rulesetId) - throws DataException { + public List findLinkableParentProcesses(String searchInput, int rulesetId) + throws DataException, DAOException, IOException { BoolQueryBuilder processQuery = new BoolQueryBuilder() .should(createSimpleWildcardQuery(ProcessTypeField.TITLE.getKey(), searchInput)); @@ -816,9 +831,14 @@ public List findLinkableParentProcesses(String searchInput, int proj processQuery.should(new MatchQueryBuilder(ProcessTypeField.ID.getKey(), searchInput).lenient(true)); } BoolQueryBuilder query = new BoolQueryBuilder().must(processQuery) - .must(new MatchQueryBuilder(ProcessTypeField.PROJECT_ID.getKey(), projectId)) .must(new MatchQueryBuilder(ProcessTypeField.RULESET.getKey(), rulesetId)); - return findByQuery(query, false); + List filteredProcesses = new ArrayList<>(); + for (ProcessDTO process : findByQueryInAllProjects(query, false)) { + if (ProcessService.canCreateChildProcess(process) || ProcessService.canCreateProcessWithCalendar(process)) { + filteredProcesses.add(process); + } + } + return filteredProcesses; } /** diff --git a/Kitodo/src/main/java/org/kitodo/production/services/data/base/ProjectSearchService.java b/Kitodo/src/main/java/org/kitodo/production/services/data/base/ProjectSearchService.java index f3cd1754d09..9e9b1245c42 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/data/base/ProjectSearchService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/data/base/ProjectSearchService.java @@ -88,6 +88,20 @@ public List findByQuery(QueryBuilder query, SortBuilder sort, Integer offset, return super.findByQuery(queryForProjects(query), sort, offset, size, related); } + /** + * Execute a search query without filtering + * for projects of the current user. + * @param query + * as QueryBuilder object + * @param related + * determines if converted object is related to some other object (if + * so, objects related to it are not included in conversion) + * @return list of found DTO objects + */ + public List findByQueryInAllProjects(QueryBuilder query, boolean related) throws DataException { + return super.findByQuery(query, related); + } + @Override public Long countDocuments(QueryBuilder query) throws DataException { return super.countDocuments(queryForProjects(query)); diff --git a/Kitodo/src/main/java/org/kitodo/production/services/security/SecurityAccessService.java b/Kitodo/src/main/java/org/kitodo/production/services/security/SecurityAccessService.java index efaaa914dbf..a2721d7e1fa 100644 --- a/Kitodo/src/main/java/org/kitodo/production/services/security/SecurityAccessService.java +++ b/Kitodo/src/main/java/org/kitodo/production/services/security/SecurityAccessService.java @@ -1095,4 +1095,13 @@ public boolean hasAuthorityToRenameMediaFiles() { public boolean hasAuthorityToAssignImportConfigurationToClient() { return hasAuthorityGlobal("assignImportConfigurationToClient"); } + + /** + * Check if the current user has the permission to link processes to parent processes of unassigned projects. + * + * @return true if the current user has the permission to link processes to parent processes of unassigned projects. + */ + public boolean hasAuthorityToLinkToProcessesOfUnassignedProjects() { + return hasAnyAuthorityForClient("linkToProcessesOfUnassignedProjects"); + } } diff --git a/Kitodo/src/main/resources/messages/messages_de.properties b/Kitodo/src/main/resources/messages/messages_de.properties index 6daee5639ae..691ee725cbc 100644 --- a/Kitodo/src/main/resources/messages/messages_de.properties +++ b/Kitodo/src/main/resources/messages/messages_de.properties @@ -940,6 +940,7 @@ projectConfiguration=Projektkonfiguration projectIsActive=Projekt ist aktiv projectIsArchived=Projekt ist inaktiv projectList=Projektliste +projectNotAssignedToCurrentUser=Das Projekt "{0}" ist Ihnen nicht zugewiesen! projectProgress=Projektentwicklung projects=Projekte projectsForMigration=Bitte Projekte f\u00FCr die Migration ausw\u00E4hlen @@ -952,7 +953,7 @@ propertySaved=Eigenschaft wurde gespeichert. createProcessForm.titleRecordLinkTab.noInsertionPosition=Sie k\u00F6nnen den Vorgang nicht mit diesem Titelsatz verkn\u00FCpfen, da der ausgew\u00E4hlte DocType an keiner Stelle als Kind erlaubt ist. createProcessForm.titleRecordLinkTab.searchButtonClick.empty=Suchanfrage leer createProcessForm.titleRecordLinkTab.searchButtonClick.error=Die Suche lief auf einen Fehler: -createProcessForm.titleRecordLinkTab.searchButtonClick.noHits=Die Suche ist abgeschlossen, aber es wurde nichts gefunden. Sie k\u00F6nnen nur Vorg\u00E4nge finden, die dasselbe Projekt und den selben Regelsatz haben. +createProcessForm.titleRecordLinkTab.searchButtonClick.noHits=Die Suche ist abgeschlossen, aber es wurden keine potentiellen Elternvor\u00E4nge gefunden. Sie k\u00F6nnen nur Vorg\u00E4nge verlinken, die denselben Regelsatz nutzen. quarter=Quartal quarters=Quartale ready=Fertig @@ -1306,6 +1307,7 @@ renameMediaThread=Medien umbenennen renameMediaForProcessesConfirmMessage=Die Mediendateien von {0} Vorg\u00E4ngen werden gem\u00E4ss ihrer Reihenfolge in den jeweiligen Vorg\u00E4ngen umbenannt. Diese Aktion kann nicht r\u00FCckg\u00E4ngig gemacht werden. M\u00F6chten Sie fortfahren? resetWorkflow=Workflow zur\u00FCcksetzen runKitodoScript=KitodoScript ausf\u00FChren +linkToProcessesOfUnassignedProjects=Verkn\u00FCpfungen mit Vorg\u00E4ngen nicht-zugewiesener Projekte erstellen viewAllAuthorities=Alle Berechtigungen anzeigen viewAllBatches=Alle Batches anzeigen diff --git a/Kitodo/src/main/resources/messages/messages_en.properties b/Kitodo/src/main/resources/messages/messages_en.properties index c1aeb833014..9d4db78f416 100644 --- a/Kitodo/src/main/resources/messages/messages_en.properties +++ b/Kitodo/src/main/resources/messages/messages_en.properties @@ -941,6 +941,7 @@ projectConfiguration=Project configuration projectIsActive=project active projectIsArchived=project inactive projectList=Projects list +projectNotAssignedToCurrentUser=Project "{0}" not assigned to current user! projectProgress=Project progress projects=Projects projectsForMigration=Please select projects for migration @@ -953,7 +954,7 @@ propertySaved=Property saved. createProcessForm.titleRecordLinkTab.noInsertionPosition=You cannot link the process with this title record because the selected DocType is not allowed as a child anywhere. createProcessForm.titleRecordLinkTab.searchButtonClick.empty=Query is empty createProcessForm.titleRecordLinkTab.searchButtonClick.error=The search ran on an error: -createProcessForm.titleRecordLinkTab.searchButtonClick.noHits=The search completed, but nothing was found. You can only find processes that have the same project and rule set. +createProcessForm.titleRecordLinkTab.searchButtonClick.noHits=The search completed, but nothing was found. You can only find processes that have the same rule set and are potential parent processes. quarter=quarter quarters=quarters ready=Ready @@ -1307,6 +1308,7 @@ renameMedia=Rename media renameMediaThread=Rename media renameMediaForProcessesConfirmMessage=The media files of {0} processes will be renamed according to their order in the individual processes. This change cannot be reverted. Do you want to continue? runKitodoScript=Execute KitodoScript +linkToProcessesOfUnassignedProjects=Link to processes of unassigned projects viewAllAuthorities=View all authorities viewAllBatches=View all batches diff --git a/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css b/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css index c349f33e67a..393fad4932f 100644 --- a/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css +++ b/Kitodo/src/main/webapp/WEB-INF/resources/css/kitodo.css @@ -1711,7 +1711,7 @@ Import form #editForm\:processFromTemplateTabView\:logicalStructure { height: calc(100% - 170px); - overflow: scroll; + overflow: auto; } #recordIdentifierMissingDialog ul { diff --git a/Kitodo/src/main/webapp/WEB-INF/templates/includes/processFromTemplate/titleRecordLink.xhtml b/Kitodo/src/main/webapp/WEB-INF/templates/includes/processFromTemplate/titleRecordLink.xhtml index 46020834f8c..7d5f33145b7 100644 --- a/Kitodo/src/main/webapp/WEB-INF/templates/includes/processFromTemplate/titleRecordLink.xhtml +++ b/Kitodo/src/main/webapp/WEB-INF/templates/includes/processFromTemplate/titleRecordLink.xhtml @@ -50,6 +50,7 @@ value="#{msgs.selectProcess}" rendered="#{not empty CreateProcessForm.titleRecordLinkTab.possibleParentProcesses}"/> @@ -189,8 +191,8 @@ title="#{MetadataLock.isLocked(parent.id) ? msgs['blocked'].concat(MetadataLock.getLockUser(parent.id).fullName) : msgs['metadataEdit']}"> diff --git a/Kitodo/src/test/java/org/kitodo/production/forms/copyprocess/TitleRecordLinkTabIT.java b/Kitodo/src/test/java/org/kitodo/production/forms/copyprocess/TitleRecordLinkTabIT.java index bf3e3914db0..8eb1b1dbf1b 100644 --- a/Kitodo/src/test/java/org/kitodo/production/forms/copyprocess/TitleRecordLinkTabIT.java +++ b/Kitodo/src/test/java/org/kitodo/production/forms/copyprocess/TitleRecordLinkTabIT.java @@ -12,29 +12,33 @@ package org.kitodo.production.forms.copyprocess; import static org.awaitility.Awaitility.await; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import java.util.Map; import java.util.Objects; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.kitodo.MockDatabase; import org.kitodo.SecurityTestUtils; +import org.kitodo.data.database.beans.Project; import org.kitodo.data.database.beans.User; +import org.kitodo.data.database.exceptions.DAOException; import org.kitodo.production.forms.createprocess.CreateProcessForm; import org.kitodo.production.forms.createprocess.TitleRecordLinkTab; +import org.kitodo.production.security.SecurityUserDetails; import org.kitodo.production.services.ServiceManager; import org.kitodo.production.services.data.ProcessService; +import org.kitodo.test.utils.ProcessTestUtils; public class TitleRecordLinkTabIT { private static final ProcessService processService = ServiceManager.getProcessService(); - + private static final String META_XML = "testParentProcessMeta.xml"; private static final String firstProcess = "First process"; + private static int parentProcessId = -1; /** * Is running before the class runs. @@ -43,7 +47,9 @@ public class TitleRecordLinkTabIT { public static void prepareDatabase() throws Exception { MockDatabase.startNode(); MockDatabase.insertProcessesFull(); - MockDatabase.insertProcessesForHierarchyTests(); + Map hierarchyProcesses = MockDatabase.insertProcessesForHierarchyTests(); + parentProcessId = hierarchyProcesses.get(MockDatabase.HIERARCHY_PARENT); + ProcessTestUtils.copyTestMetadataFile(parentProcessId, META_XML); MockDatabase.setUpAwaitility(); User userOne = ServiceManager.getUserService().getById(1); SecurityTestUtils.addUserDataToSecurityContext(userOne, 1); @@ -58,6 +64,7 @@ public static void prepareDatabase() throws Exception { */ @AfterClass public static void cleanDatabase() throws Exception { + ProcessTestUtils.removeTestProcess(parentProcessId); MockDatabase.stopNode(); MockDatabase.cleanDatabase(); } @@ -70,24 +77,55 @@ public void shouldChooseParentProcess() { TitleRecordLinkTab testedTitleRecordLinkTab = new TitleRecordLinkTab(null); testedTitleRecordLinkTab.setChosenParentProcess("1"); testedTitleRecordLinkTab.chooseParentProcess(); - assertFalse("titleRecordProcess is null!", Objects.isNull(testedTitleRecordLinkTab.getTitleRecordProcess())); - assertEquals("titleRecordProcess has wrong ID!", (Integer) 1, + Assert.assertFalse("titleRecordProcess is null!", Objects.isNull(testedTitleRecordLinkTab.getTitleRecordProcess())); + Assert.assertEquals("titleRecordProcess has wrong ID!", (Integer) 1, testedTitleRecordLinkTab.getTitleRecordProcess().getId()); } @Test - @Ignore public void shouldSearchForParentProcesses() throws Exception { CreateProcessForm createProcessForm = new CreateProcessForm(); createProcessForm.setProject(ServiceManager.getProjectService().getById(1)); createProcessForm.setTemplate(ServiceManager.getTemplateService().getById(1)); - TitleRecordLinkTab testedTitleRecordLinkTab = new TitleRecordLinkTab(null); + TitleRecordLinkTab testedTitleRecordLinkTab = new TitleRecordLinkTab(createProcessForm); testedTitleRecordLinkTab.setSearchQuery("HierarchyParent"); testedTitleRecordLinkTab.searchForParentProcesses(); - assertEquals("Wrong number of possibleParentProcesses found!", 1, + Assert.assertEquals("Wrong number of possibleParentProcesses found!", 1, testedTitleRecordLinkTab.getPossibleParentProcesses().size()); - assertEquals("Wrong possibleParentProcesses found!", "4", + Assert.assertEquals("Wrong possibleParentProcesses found!", "4", testedTitleRecordLinkTab.getPossibleParentProcesses().get(0).getValue()); } + + /** + * Verify that user can only link to parent processes of unassigned projects when he has the corresponding + * permission/authority. + * @throws DAOException when loading test objects from database fails + */ + @Test + public void shouldPreventLinkingToParentProcessOfUnassignedProject() throws DAOException { + Project firstProject = ServiceManager.getProjectService().getById(1); + SecurityUserDetails user = ServiceManager.getUserService().getAuthenticatedUser(); + user.getProjects().remove(firstProject); + + TitleRecordLinkTab testedTitleRecordLinkTab = searchForHierarchyParent(); + + Assert.assertEquals("Wrong number of potential parent processes found!", 1, + testedTitleRecordLinkTab.getPossibleParentProcesses().size()); + Assert.assertTrue("Process of unassigned project should be deactivated in TitleRecordLinkTab!", + testedTitleRecordLinkTab.getPossibleParentProcesses().get(0).isDisabled()); + + // re-add first project to user + user.getProjects().add(firstProject); + } + + private TitleRecordLinkTab searchForHierarchyParent() throws DAOException { + CreateProcessForm createProcessForm = new CreateProcessForm(); + createProcessForm.setProject(ServiceManager.getProjectService().getById(2)); + createProcessForm.setTemplate(ServiceManager.getTemplateService().getById(1)); + TitleRecordLinkTab testedTitleRecordLinkTab = new TitleRecordLinkTab(createProcessForm); + testedTitleRecordLinkTab.setSearchQuery("HierarchyParent"); + testedTitleRecordLinkTab.searchForParentProcesses(); + return testedTitleRecordLinkTab; + } } diff --git a/Kitodo/src/test/java/org/kitodo/production/services/data/AuthorityServiceIT.java b/Kitodo/src/test/java/org/kitodo/production/services/data/AuthorityServiceIT.java index bdd9842f409..d9f74204a07 100644 --- a/Kitodo/src/test/java/org/kitodo/production/services/data/AuthorityServiceIT.java +++ b/Kitodo/src/test/java/org/kitodo/production/services/data/AuthorityServiceIT.java @@ -67,7 +67,7 @@ public void shouldGetByTitle() throws Exception { @Test public void shouldNotGetByTitle() throws Exception { exception.expect(DAOException.class); - exception.expectMessage("Object cannot be found in database"); + exception.expectMessage("Authority 'viewAllStuff_globalAssignable' cannot be found in database"); authorityService.getByTitle("viewAllStuff_globalAssignable"); } diff --git a/Kitodo/src/test/java/org/kitodo/production/services/data/ImportServiceTest.java b/Kitodo/src/test/java/org/kitodo/production/services/data/ImportServiceTest.java new file mode 100644 index 00000000000..7acb16ccedc --- /dev/null +++ b/Kitodo/src/test/java/org/kitodo/production/services/data/ImportServiceTest.java @@ -0,0 +1,57 @@ +/* + * (c) Kitodo. Key to digital objects e. V. + * + * This file is part of the Kitodo project. + * + * It is licensed under GNU General Public License version 3 or later. + * + * For the full copyright and license information, please read the + * GPL3-License.txt file that was distributed with this source code. + */ + + +package org.kitodo.production.services.data; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.kitodo.production.dto.ProcessDTO; +import org.kitodo.production.dto.ProjectDTO; + +public class ImportServiceTest { + + /** + * Test that the list of process DTOs is sorted based on a provided projectId. + * Processes which match the provided projectId should come first. + */ + @Test + public void shouldSortProcessesWithProvidedProjectIdFirst() { + + ProjectDTO projectOne = new ProjectDTO(); + projectOne.setId(10); + ProjectDTO projectTwo = new ProjectDTO(); + projectTwo.setId(9); + ProjectDTO projectThree = new ProjectDTO(); + projectThree.setId(8); + + ProcessDTO processOne = new ProcessDTO(); + processOne.setProject(projectOne); + ProcessDTO processTwo = new ProcessDTO(); + processTwo.setProject(projectTwo); + ProcessDTO processThree = new ProcessDTO(); + processThree.setProject(projectThree); + + List processes = new ArrayList<>(Arrays.asList(processOne, processTwo, processThree)); + + ImportService importService = new ImportService(); + List sortedProcesses = importService.sortProcessesByProjectID(processes, 9); + + int projectIdOfFirstProcess = sortedProcesses.get(0).getProject().getId(); + + assertEquals(9, projectIdOfFirstProcess, "Process not sorted based on provided projectId"); + } +} diff --git a/Kitodo/src/test/java/org/kitodo/production/services/data/ProcessServiceIT.java b/Kitodo/src/test/java/org/kitodo/production/services/data/ProcessServiceIT.java index 51fb6c62c55..ba94294fd75 100644 --- a/Kitodo/src/test/java/org/kitodo/production/services/data/ProcessServiceIT.java +++ b/Kitodo/src/test/java/org/kitodo/production/services/data/ProcessServiceIT.java @@ -317,9 +317,9 @@ public void shouldNotFindByTokenizedPropertyTitleAndWrongValue() throws DataExce } @Test - public void shouldFindLinkableParentProcesses() throws DataException { + public void shouldFindLinkableParentProcesses() throws DataException, DAOException, IOException { assertEquals("Processes were not found in index!", 1, - processService.findLinkableParentProcesses("HierarchyParent", 1, 1).size()); + processService.findLinkableParentProcesses(MockDatabase.HIERARCHY_PARENT, 1).size()); } @Ignore("for second process is attached task which is processed by blocked user") diff --git a/Kitodo/src/test/java/org/kitodo/selenium/AddingST.java b/Kitodo/src/test/java/org/kitodo/selenium/AddingST.java index 34e04dc89af..1931c8b5f57 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/AddingST.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/AddingST.java @@ -70,7 +70,7 @@ public class AddingST extends BaseTestSelenium { private static RoleEditPage roleEditPage; private static UserEditPage userEditPage; private static ImportConfigurationEditPage importConfigurationEditPage; - private static final String TEST_METADATA_FILE = "testMetadataFileServiceTest.xml"; + private static final String TEST_METADATA_FILE = "testMultiVolumeWorkMeta.xml"; private static int secondProcessId = -1; private static final String PICA_PPN = "pica.ppn"; private static final String PICA_XML = "picaxml"; diff --git a/Kitodo/src/test/java/org/kitodo/selenium/ImportingST.java b/Kitodo/src/test/java/org/kitodo/selenium/ImportingST.java index cc3e9418ab5..791042a7fcc 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/ImportingST.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/ImportingST.java @@ -172,6 +172,7 @@ public void checkHierarchyImport() throws Exception { importPage.decreaseImportDepth(); importPage.getSearchButton().click(); assertTrue("Hierarchy panel should be visible", importPage.isHierarchyPanelVisible()); + importPage.addPpnAndTitle(); String parentTitle = importPage.getProcessTitle(); Pages.getProcessFromTemplatePage().save(); processesPage.applyFilter(parentTitle); diff --git a/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessFromTemplatePage.java b/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessFromTemplatePage.java index 7a309d4a65e..15823e10d3b 100644 --- a/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessFromTemplatePage.java +++ b/Kitodo/src/test/java/org/kitodo/selenium/testframework/pages/ProcessFromTemplatePage.java @@ -179,7 +179,7 @@ public String createProcess() throws Exception { .atMost(3, TimeUnit.SECONDS).ignoreExceptions() .until(() -> docTypeSelect.findElement(By.cssSelector(CSS_SELECTOR_DROPDOWN_TRIGGER)).isEnabled()); clickElement(docTypeSelect.findElement(By.cssSelector(CSS_SELECTOR_DROPDOWN_TRIGGER))); - clickElement(Browser.getDriver().findElement(By.id(docTypeSelect.getAttribute("id") + "_3"))); + clickElement(Browser.getDriver().findElement(By.id(docTypeSelect.getAttribute("id") + "_2"))); await("Page ready").pollDelay(150, TimeUnit.MILLISECONDS).atMost(10, TimeUnit.SECONDS).ignoreExceptions() .until(() -> isDisplayed.test(processFromTemplateTabView)); @@ -197,6 +197,14 @@ public String createProcess() throws Exception { return generatedTitle; } + /** + * Add metadata values for PPN and title. + */ + public void addPpnAndTitle() { + titleSortInput.sendKeys("TestProcess"); + ppnDigitalInput.sendKeys("12345"); + } + /** * Creates a process as child. * @@ -210,14 +218,11 @@ public String createProcessAsChild(String parentProcessTitle) throws Exception { .atMost(3, TimeUnit.SECONDS).ignoreExceptions() .until(() -> docTypeSelect.findElement(By.cssSelector(CSS_SELECTOR_DROPDOWN_TRIGGER)).isEnabled()); clickElement(docTypeSelect.findElement(By.cssSelector(CSS_SELECTOR_DROPDOWN_TRIGGER))); - clickElement(Browser.getDriver().findElement(By.id(docTypeSelect.getAttribute("id") + "_3"))); + clickElement(Browser.getDriver().findElement(By.id(docTypeSelect.getAttribute("id") + "_0"))); await("Page ready").pollDelay(150, TimeUnit.MILLISECONDS).atMost(10, TimeUnit.SECONDS).ignoreExceptions() .until(() -> isDisplayed.test(processFromTemplateTabView)); - titleInput.sendKeys("TestProcessChild"); titleSortInput.sendKeys("TestProcessChild"); - ppnAnalogInput.sendKeys("123456"); - ppnDigitalInput.sendKeys("123456"); generateTitleButton.click(); await("Wait for title generation").pollDelay(3, TimeUnit.SECONDS).atMost(10, TimeUnit.SECONDS) diff --git a/Kitodo/src/test/resources/metadata/metadataFiles/multivalued_metadata.xml b/Kitodo/src/test/resources/metadata/metadataFiles/multivalued_metadata.xml index adeb07d1a41..a6de83c137e 100644 --- a/Kitodo/src/test/resources/metadata/metadataFiles/multivalued_metadata.xml +++ b/Kitodo/src/test/resources/metadata/metadataFiles/multivalued_metadata.xml @@ -32,7 +32,7 @@ - + diff --git a/Kitodo/src/test/resources/rulesets/ruleset_test.xml b/Kitodo/src/test/resources/rulesets/ruleset_test.xml index ab70eded5ec..ae346f9ce23 100644 --- a/Kitodo/src/test/resources/rulesets/ruleset_test.xml +++ b/Kitodo/src/test/resources/rulesets/ruleset_test.xml @@ -145,6 +145,15 @@ + + + + + + + + +