diff --git a/README.md b/README.md index 016e34a8f..3a120b260 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ ubo-cli/target/bin/ubo.sh update permission read for id default with rulefile ub ubo-cli/target/bin/ubo.sh update permission writedb for id default with rulefile ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only ubo-cli/target/bin/ubo.sh update permission deletedb for id default with rulefile ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only ubo-cli/target/bin/ubo.sh update permission read for id restapi:/ with rulefile ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed +ubo-cli/target/bin/ubo.sh update permission read for id restapi:/classifications with rulefile ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed ``` ## MyCoRe-Solr-Configuration diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 45b04a6d7..b4cb86872 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -136,12 +136,13 @@ function setUpMyCoRe { sed -ri "s/META-INF\/mycore-ifs-mappings.xml<\/mapping-file>//" "${PERSISTENCE_XML}" /opt/ubo/ubo-cli/target/bin/ubo.sh init superuser /opt/ubo/ubo-cli/target/bin/ubo.sh update all classifications from directory /opt/ubo/ubo-cli/src/main/setup/classifications - /opt/ubo/ubo-cli/target/bin/ubo.sh update permission update permission create-mods for id POOLPRIVILEGE with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed - /opt/ubo/ubo-cli/target/bin/ubo.sh update permission update permission create-users for id POOLPRIVILEGE with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only - /opt/ubo/ubo-cli/target/bin/ubo.sh update permission update permission administrate-users for id POOLPRIVILEGE with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only - /opt/ubo/ubo-cli/target/bin/ubo.sh update permission update permission read for id default with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed - /opt/ubo/ubo-cli/target/bin/ubo.sh update permission update permission writedb for id default with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only - /opt/ubo/ubo-cli/target/bin/ubo.sh update permission update permission read for id restapi:/ with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed + /opt/ubo/ubo-cli/target/bin/ubo.sh update permission create-mods for id POOLPRIVILEGE with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed + /opt/ubo/ubo-cli/target/bin/ubo.sh update permission create-users for id POOLPRIVILEGE with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only + /opt/ubo/ubo-cli/target/bin/ubo.sh update permission administrate-users for id POOLPRIVILEGE with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only + /opt/ubo/ubo-cli/target/bin/ubo.sh update permission read for id default with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed + /opt/ubo/ubo-cli/target/bin/ubo.sh update permission writedb for id default with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only + /opt/ubo/ubo-cli/target/bin/ubo.sh update permission read for id restapi:/ with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed + /opt/ubo/ubo-cli/target/bin/ubo.sh update permission read for id restapi:/classifications with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed /opt/ubo/ubo-cli/target/bin/ubo.sh update permission deletedb for id default with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only /opt/ubo/ubo-cli/target/bin/ubo.sh reload solr configuration main in core main } diff --git a/ubo-common/src/main/java/org/mycore/ubo/NewPublicationWizard.java b/ubo-common/src/main/java/org/mycore/ubo/NewPublicationWizard.java index 2af4c8b94..0b83e67f8 100644 --- a/ubo-common/src/main/java/org/mycore/ubo/NewPublicationWizard.java +++ b/ubo-common/src/main/java/org/mycore/ubo/NewPublicationWizard.java @@ -126,8 +126,13 @@ private String buildQuery(Element mods) { query.append("dedup:").append(MCRSolrUtils.escapeSearchValue(criterion.getKey())).append(" OR "); } - query.append("(title:\"").append(getByXPath(mods, "mods:titleInfo/mods:title")).append('"'); - query.append(" AND person:\"").append(getByXPath(mods, "mods:name/mods:namePart")).append("\"))"); + query.append("(title:\"") + .append(MCRSolrUtils.escapeSearchValue(getByXPath(mods, "mods:titleInfo/mods:title"))) + .append('"'); + + query.append(" AND person:\"") + .append(MCRSolrUtils.escapeSearchValue(getByXPath(mods, "mods:name/mods:namePart"))) + .append("\"))"); return query.toString(); } diff --git a/ubo-common/src/main/java/org/mycore/ubo/importer/ImportListJobAction.java b/ubo-common/src/main/java/org/mycore/ubo/importer/ImportListJobAction.java index c17166b0c..0be16778c 100644 --- a/ubo-common/src/main/java/org/mycore/ubo/importer/ImportListJobAction.java +++ b/ubo-common/src/main/java/org/mycore/ubo/importer/ImportListJobAction.java @@ -6,6 +6,7 @@ import org.jdom2.Element; import org.jdom2.input.SAXBuilder; import org.mycore.common.MCRMailer; +import org.mycore.common.MCRTransactionHelper; import org.mycore.common.config.MCRConfiguration2; import org.mycore.frontend.MCRFrontendUtil; import org.mycore.services.i18n.MCRTranslation; @@ -66,8 +67,15 @@ public void execute() throws ExecutionException { importJob.enrich(); } - importJob.saveAndIndex(); - sendMail(importJob); + try { + MCRTransactionHelper.beginTransaction(); + importJob.savePublications(); + MCRTransactionHelper.commitTransaction(); + sendMail(importJob); + } catch (Exception e) { + LOGGER.error("Error while saving publications", e); + MCRTransactionHelper.rollbackTransaction(); + } } catch (Exception e) { LOGGER.error("Could not transform form input", e); } diff --git a/ubo-common/src/main/java/org/mycore/ubo/local/LocalService.java b/ubo-common/src/main/java/org/mycore/ubo/local/LocalService.java index b802ebe6f..fb676b1ec 100644 --- a/ubo-common/src/main/java/org/mycore/ubo/local/LocalService.java +++ b/ubo-common/src/main/java/org/mycore/ubo/local/LocalService.java @@ -1,12 +1,7 @@ package org.mycore.ubo.local; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import javax.naming.OperationNotSupportedException; - +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.jdom2.Element; import org.mycore.common.config.MCRConfiguration2; import org.mycore.ubo.picker.IdentityService; @@ -17,7 +12,14 @@ import org.mycore.user2.MCRUserAttribute; import org.mycore.user2.MCRUserManager; +import javax.naming.OperationNotSupportedException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + public class LocalService implements IdentityService { + private final static Logger LOGGER = LogManager.getLogger(LocalService.class); private final String USER_FIRST_NAME_ATTR = "firstName"; @@ -51,7 +53,16 @@ public PersonSearchResult searchPerson(String query, MCRRealm realm) { List personResults = matchingUsers.stream().map(user -> { PersonSearchResult.PersonResult personSearchResult = new PersonSearchResult.PersonResult(this); - personSearchResult.pid = user.getUserAttribute("id_" + LEAD_ID); + List leadIdAttributes = user.getAttributes() + .stream() + .filter(attr -> ("id_" + LEAD_ID).equals(attr.getName())) + .toList(); + + if (leadIdAttributes.size() > 1) { + LOGGER.warn("Found more than one {} for user {}", ("id_" + LEAD_ID), user.getUserID()); + } + + personSearchResult.pid = leadIdAttributes.size() > 0 ? leadIdAttributes.get(0).getValue() : null; personSearchResult.displayName = user.getRealName().length() > 0 ? user.getRealName() : user.getUserName(); user.getAttributes().stream() diff --git a/ubo-common/src/main/resources/META-INF/resources/edit-admin.xed b/ubo-common/src/main/resources/META-INF/resources/edit-admin.xed index f60d2aa89..854bbc6ce 100644 --- a/ubo-common/src/main/resources/META-INF/resources/edit-admin.xed +++ b/ubo-common/src/main/resources/META-INF/resources/edit-admin.xed @@ -118,6 +118,7 @@ + diff --git a/ubo-common/src/main/resources/META-INF/resources/edit-publication.xed b/ubo-common/src/main/resources/META-INF/resources/edit-publication.xed index b1e595149..371c8dd47 100644 --- a/ubo-common/src/main/resources/META-INF/resources/edit-publication.xed +++ b/ubo-common/src/main/resources/META-INF/resources/edit-publication.xed @@ -147,7 +147,7 @@ - + diff --git a/ubo-common/src/main/resources/META-INF/resources/import-editor.xed b/ubo-common/src/main/resources/META-INF/resources/import-editor.xed index 7cb8dcf01..f71338744 100644 --- a/ubo-common/src/main/resources/META-INF/resources/import-editor.xed +++ b/ubo-common/src/main/resources/META-INF/resources/import-editor.xed @@ -35,6 +35,10 @@ ]]> + + + + @@ -264,13 +268,10 @@ in - + @@ -781,13 +782,10 @@ - + @@ -801,13 +799,10 @@ - + diff --git a/ubo-common/src/main/resources/META-INF/resources/js/language-search.js b/ubo-common/src/main/resources/META-INF/resources/js/language-search.js new file mode 100644 index 000000000..4cd2e0018 --- /dev/null +++ b/ubo-common/src/main/resources/META-INF/resources/js/language-search.js @@ -0,0 +1,280 @@ +/* + * This file is part of *** M y C o R e *** + * See http://www.mycore.de/ for details. + * + * MyCoRe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MyCoRe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MyCoRe. If not, see . + */ + +/** + * @typedef {Object} ClassificationLabel + * @property {string} lang + * @property {string} text + * @property {string} description + */ + +/** + * @typedef {Object} ClassificationCategory + * @property {string} ID + * @property {Array} labels + * @property {string} categories + */ + +/** + * @typedef {Object} ClassificationResponse + * @property {string} ID + * @property {Array} labels + * @property {Array} categories + */ + +/** + * Class to create a language search input. This input will show a list of languages to choose from. It expects the following + * structure: + * + */ +class LanguageSearchInput { + + /** + * @type {HTMLDataListElement} + */ + static dataList; + /** + * @type {Map} + */ + static labelIdMap; + + /** + * @type {Map} + */ + static idLabelMap; + /** + * @type {HTMLInputElement} + */ + hiddenInput; + /** + * @type {HTMLDivElement} + */ + root; + /** + * @type {HTMLInputElement} + */ + searchInput; + /** + * @type {Array} + */ + preferredLanguages; + /** + * Sometimes the labelIdMap and idLabelMap are not yet available. This promise will resolve once they are. + * @type {Promise<{ + * labelIdMap: Map, + * idLabelMap: Map, + * }>} + */ + static mapPromise; + + /** + * Creates a search input for languages. + * @param {HTMLDivElement} elem + */ + constructor(elem) { + this.root = elem; + this.hiddenInput = elem.querySelector('input[class="language-search-input"]'); + + if (elem.getAttribute("data-preferred-languages") != null) { + this.preferredLanguages = elem.getAttribute("data-preferred-languages").split(','); + } else { + this.preferredLanguages = []; + } + + if (this.hiddenInput == null) { + console.error('No input found in language-search-input'); + return; + } + + + this.initializeForm().then(() => { + console.log('Form initialized'); + }); + + } + + /** + * Finds the best matching label from the given array of labels. + * @param labelArray {Array} The array of labels to search in. + */ + static findBestMatchingLabel(labelArray) { + let bestMatch = labelArray[0]; + + labelArray.forEach(label => { + if (label.lang === window["currentLang"]) { + bestMatch = label; + } + }); + + return bestMatch; + } + + /** + * Checks if the given search input is valid. + * @param searchInput {HTMLInputElement} The search input to check. + * @returns {boolean|boolean|*} True if the input is valid, false otherwise. + */ + static isValidInput(searchInput) { + return LanguageSearchInput.labelIdMap.has(searchInput.value); + } + + /** + * Modifies the given search input to indicate if the input is valid or not. + * @param searchInput {HTMLInputElement} The search input to modify. + * @param isValid {boolean} True if the input is valid + */ + static setInputValidation(searchInput, isValid) { + if (isValid) { + searchInput.classList.remove('is-invalid'); + } else { + searchInput.classList.add('is-invalid'); + } + } + + async initializeForm() { + const baseURL = window['webApplicationBaseURL']; + const currentLang = window["currentLang"]; + + if (!LanguageSearchInput.dataList) { + LanguageSearchInput.dataList = document.createElement('datalist'); + LanguageSearchInput.dataList.id = 'language-search-list'; + + /** + * Used to queue up the resolvers for the mapPromise + * @type {Array, + * idLabelMap: Map, + * }>>} + */ + const resolverList = []; + + LanguageSearchInput.mapPromise = new Promise(async (resolve, reject) => { + if(LanguageSearchInput.labelIdMap && LanguageSearchInput.idLabelMap){ + resolve({ + labelIdMap: LanguageSearchInput.labelIdMap, + idLabelMap: LanguageSearchInput.idLabelMap + }); + return; + } + resolverList.push(resolve); + }); + + const baseURL = window['webApplicationBaseURL']; + const response = await fetch(baseURL + 'api/v2/classifications/rfc5646', { + method: 'GET', + headers: { + 'Accept': 'application/json' + } + }); + + /** + * @type {ClassificationResponse} + */ + const data = await response.json(); + + + LanguageSearchInput.labelIdMap = /** @type {Map} */ new Map(); + + LanguageSearchInput.idLabelMap = /** @type {Map} */ new Map(); + /** + * @type {Array<{el: HTMLOptionElement, category: ClassificationCategory}>} + */ + const prependLater = []; + data.categories.forEach(category => { + const optionElement = document.createElement("option"); + const label = LanguageSearchInput.findBestMatchingLabel(category.labels).text; + optionElement.value = label; + optionElement.text = label; + LanguageSearchInput.labelIdMap.set(label, category.ID); + LanguageSearchInput.idLabelMap.set(category.ID, label); + const preferredLanguagePos = this.preferredLanguages.indexOf(category.ID); + if (preferredLanguagePos == -1) { + LanguageSearchInput.dataList.append(optionElement); + } else { + prependLater.push({el:optionElement , category:category}); + } + }); + + resolverList.forEach((resolve) => { + resolve({ + labelIdMap: LanguageSearchInput.labelIdMap, + idLabelMap: LanguageSearchInput.idLabelMap + }); + }); + + prependLater.sort((a,b) => { + return this.preferredLanguages.indexOf(b.category.ID) - this.preferredLanguages.indexOf(a.category.ID); + }).forEach((el) => { + LanguageSearchInput.dataList.prepend(el.el); + }); + + this.root.append(LanguageSearchInput.dataList); + } + + this.searchInput = document.createElement('input'); + this.searchInput.type = 'text'; + this.searchInput.classList.add('form-control'); + this.searchInput.classList.add('language-search-input'); + this.searchInput.setAttribute('list', 'language-search-list'); + this.root.append(this.searchInput); + + fetch(`${baseURL}rsc/locale/translate/${currentLang}/edit.language.placeholder`) + .then(response => response.text()) + .then(translation => { + this.searchInput.placeholder = translation; + }); + + + if(this.hiddenInput.value != null && this.hiddenInput.value.trim().length > 0) { + let idLabelMap = LanguageSearchInput.idLabelMap; + if(!LanguageSearchInput.idLabelMap){ + const maps = await LanguageSearchInput.mapPromise; + idLabelMap = maps.idLabelMap; + } + if(idLabelMap.has(this.hiddenInput.value)) { + this.searchInput.value = idLabelMap.get(this.hiddenInput.value.trim()); + } + } + + this.searchInput.addEventListener('change', () => { + if (this.searchInput.value.trim().length === 0) { + this.hiddenInput.value = ''; + LanguageSearchInput.setInputValidation(this.searchInput, true); + return; + } + + if (LanguageSearchInput.isValidInput(this.searchInput)) { + this.hiddenInput.value = LanguageSearchInput.labelIdMap.get(this.searchInput.value); + LanguageSearchInput.setInputValidation(this.searchInput, true); + return; + } + + LanguageSearchInput.setInputValidation(this.searchInput, false); + }); + } +} + +document.addEventListener('DOMContentLoaded', function () { + + document.querySelectorAll('.language-search').forEach(function (input) { + new LanguageSearchInput(input); + }); + +}); diff --git a/ubo-common/src/main/resources/config/ubo-common/messages_de.properties b/ubo-common/src/main/resources/config/ubo-common/messages_de.properties index c571ee926..e157de1f5 100644 --- a/ubo-common/src/main/resources/config/ubo-common/messages_de.properties +++ b/ubo-common/src/main/resources/config/ubo-common/messages_de.properties @@ -345,6 +345,7 @@ edit.diss.referee = Bitte geben Sie auch den/die Betreuer*in bzw. D edit.document = *Dokumententyp(en) edit.keyword = Schlag-/Stichw\u00F6rter edit.language = (*)Sprache +edit.language.placeholder = Suchen.. edit.legend.delete = Wenn Sie diesen Vorgang best\u00E4tigen, wird die Nutzerkennung gel\u00F6scht. edit.legend.enter = Bitte geben Sie hier die Daten Ihrer Publikation ein.\r\n\u0009 Pflichtfelder sind mit einem (*) gekennzeichnet.\r\n\u0009 Durch klicken auf [+] k\u00F6nnen Sie ein Eingabefeld wiederholen! edit.legend.enterDiss = Bitte geben Sie hier die Daten Ihrer Dissertation ein.\r\n\u0009 Pflichtfelder sind mit einem (*) gekennzeichnet.\r\n\u0009 Durch klicken auf [+] k\u00F6nnen Sie ein Eingabefeld wiederholen! diff --git a/ubo-common/src/main/resources/config/ubo-common/messages_en.properties b/ubo-common/src/main/resources/config/ubo-common/messages_en.properties index f4e686b42..83f648962 100644 --- a/ubo-common/src/main/resources/config/ubo-common/messages_en.properties +++ b/ubo-common/src/main/resources/config/ubo-common/messages_en.properties @@ -343,6 +343,7 @@ edit.diss.referee = Please enter also y edit.document = *Document type(s) edit.keyword = Subject heading/keywords edit.language = *Language +edit.language.placeholder = search.. edit.legend.delete = If you confirm this process, the user will be deleted. edit.legend.enter = Please, enter the data of your publication here.\r\n With (*) marked fields are required fields.\r\n By clicking on [+] you can repeat an input field! edit.legend.enterDiss = Please, enter the data of your dissertation here.\r\n With (*) marked fields are required fields.\r\n By clicking on [+] you can repeat an input field! diff --git a/ubo-common/src/main/resources/xsl/mods-dc.xsl b/ubo-common/src/main/resources/xsl/mods-dc.xsl index 8587005de..b713ab45e 100644 --- a/ubo-common/src/main/resources/xsl/mods-dc.xsl +++ b/ubo-common/src/main/resources/xsl/mods-dc.xsl @@ -161,9 +161,13 @@ - - - + + + + + + + diff --git a/ubo-common/src/main/resources/xsl/mods-display.xsl b/ubo-common/src/main/resources/xsl/mods-display.xsl index 70e0743b1..e886b7511 100644 --- a/ubo-common/src/main/resources/xsl/mods-display.xsl +++ b/ubo-common/src/main/resources/xsl/mods-display.xsl @@ -53,8 +53,10 @@ + + + onclick="location.assign('{$WebApplicationBaseURL}servlets/solr/select?sort=modified+desc&q={encoder:encode(concat($fq, '+genre:"', $genre, '"'))}')"> in @@ -1663,7 +1665,17 @@ - + + + + + + + + + + + , diff --git a/ubo-common/src/main/resources/xsl/mods-preprocessor.xsl b/ubo-common/src/main/resources/xsl/mods-preprocessor.xsl index 5e4ac8dfe..323f47960 100644 --- a/ubo-common/src/main/resources/xsl/mods-preprocessor.xsl +++ b/ubo-common/src/main/resources/xsl/mods-preprocessor.xsl @@ -15,7 +15,7 @@ - + diff --git a/ubo-common/src/main/resources/xsl/response-facets.xsl b/ubo-common/src/main/resources/xsl/response-facets.xsl index 82128bf58..bd7660f28 100644 --- a/ubo-common/src/main/resources/xsl/response-facets.xsl +++ b/ubo-common/src/main/resources/xsl/response-facets.xsl @@ -9,7 +9,8 @@ xmlns:str="xalan://java.lang.String" xmlns:mcrxml="xalan://org.mycore.common.xml.MCRXMLFunctions" xmlns:dozbib="xalan://org.mycore.ubo.DozBibCommands" - exclude-result-prefixes="dozbib xsl xalan i18n encoder mcrxml str"> + xmlns:solrUtil="xalan://org.mycore.solr.MCRSolrUtils" + exclude-result-prefixes="dozbib xsl xalan i18n encoder mcrxml str solrUtil"> @@ -81,11 +82,11 @@ - + - + @@ -194,7 +195,7 @@ - + @@ -217,14 +218,24 @@ - + + + scroll-on-hover + + + - + + + scroll-on-hover + + + @@ -305,23 +316,15 @@ - - - - scroll-on-hover - + - - - scroll-on-hover - + - - +