diff --git a/README.md b/README.md index 51c09fd677f7..abbe997c7351 100644 --- a/README.md +++ b/README.md @@ -128,4 +128,7 @@ The full license is available in the [LICENSE](LICENSE) file or online at http:/ DSpace uses third-party libraries which may be distributed under different licenses. Those licenses are listed in the [LICENSES_THIRD_PARTY](LICENSES_THIRD_PARTY) file. - \ No newline at end of file + +# Additional notes + +This project is tested with BrowserStack. diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index c7b93f6ac377..6ac36d129ade 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -339,8 +339,8 @@ org.piwik.java.tracking - matomo-java-tracker - 2.0 + matomo-java-tracker-java11 + 3.4.0 org.apache.logging.log4j diff --git a/dspace-api/src/main/java/org/dspace/administer/FileDownloader.java b/dspace-api/src/main/java/org/dspace/administer/FileDownloader.java new file mode 100644 index 000000000000..fb592627adef --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/administer/FileDownloader.java @@ -0,0 +1,229 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.administer; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; +import java.util.stream.Stream; + +import org.apache.commons.cli.ParseException; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.Bitstream; +import org.dspace.content.BitstreamFormat; +import org.dspace.content.Bundle; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.BitstreamFormatService; +import org.dspace.content.service.BitstreamService; +import org.dspace.content.service.ItemService; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.EPersonService; +import org.dspace.identifier.IdentifierNotFoundException; +import org.dspace.identifier.IdentifierNotResolvableException; +import org.dspace.identifier.factory.IdentifierServiceFactory; +import org.dspace.identifier.service.IdentifierService; +import org.dspace.scripts.DSpaceRunnable; +import org.dspace.scripts.configuration.ScriptConfiguration; +import org.dspace.utils.DSpace; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class FileDownloader extends DSpaceRunnable { + + private static final Logger log = LoggerFactory.getLogger(FileDownloader.class); + private boolean help = false; + private UUID itemUUID; + private int workspaceID; + private String pid; + private URI uri; + private String epersonMail; + private String bitstreamName; + private EPersonService epersonService; + private ItemService itemService; + private WorkspaceItemService workspaceItemService; + private IdentifierService identifierService; + private BitstreamService bitstreamService; + private BitstreamFormatService bitstreamFormatService; + private final HttpClient httpClient = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.NORMAL) + .build(); + + /** + * This method will return the Configuration that the implementing DSpaceRunnable uses + * + * @return The {@link ScriptConfiguration} that this implementing DspaceRunnable uses + */ + @Override + public FileDownloaderConfiguration getScriptConfiguration() { + return new DSpace().getServiceManager().getServiceByName("file-downloader", + FileDownloaderConfiguration.class); + } + + /** + * This method has to be included in every script and handles the setup of the script by parsing the CommandLine + * and setting the variables + * + * @throws ParseException If something goes wrong + */ + @Override + public void setup() throws ParseException { + log.debug("Setting up {}", FileDownloader.class.getName()); + if (commandLine.hasOption("h")) { + help = true; + return; + } + + if (!commandLine.hasOption("u")) { + throw new ParseException("No URL option has been provided"); + } + + if (!commandLine.hasOption("i") && !commandLine.hasOption("w") && !commandLine.hasOption("p")) { + throw new ParseException("No item id option has been provided"); + } + + if (getEpersonIdentifier() == null && !commandLine.hasOption("e")) { + throw new ParseException("No eperson option has been provided"); + } + + + this.epersonService = EPersonServiceFactory.getInstance().getEPersonService(); + this.itemService = ContentServiceFactory.getInstance().getItemService(); + this.workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService(); + this.bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); + this.bitstreamFormatService = ContentServiceFactory.getInstance().getBitstreamFormatService(); + this.identifierService = IdentifierServiceFactory.getInstance().getIdentifierService(); + + try { + uri = new URI(commandLine.getOptionValue("u")); + } catch (URISyntaxException e) { + throw new ParseException("The provided URL is not a valid URL"); + } + + if (commandLine.hasOption("i")) { + itemUUID = UUID.fromString(commandLine.getOptionValue("i")); + } else if (commandLine.hasOption("w")) { + workspaceID = Integer.parseInt(commandLine.getOptionValue("w")); + } else if (commandLine.hasOption("p")) { + pid = commandLine.getOptionValue("p"); + } + + epersonMail = commandLine.getOptionValue("e"); + + if (commandLine.hasOption("n")) { + bitstreamName = commandLine.getOptionValue("n"); + } + } + + /** + * This method has to be included in every script and this will be the main execution block for the script that'll + * contain all the logic needed + * + * @throws Exception If something goes wrong + */ + @Override + public void internalRun() throws Exception { + log.debug("Running {}", FileDownloader.class.getName()); + if (help) { + printHelp(); + return; + } + + Context context = new Context(); + context.setCurrentUser(getEperson(context)); + + //find the item by the given id + Item item = findItem(context); + if (item == null) { + throw new IllegalArgumentException("No item found for the given ID"); + } + + HttpRequest request = HttpRequest.newBuilder() + .uri(uri) + .build(); + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); + + if (response.statusCode() >= 400) { + throw new IllegalArgumentException("The provided URL returned a status code of " + response.statusCode()); + } + + //use the provided value, the content-disposition header, the last part of the uri + if (bitstreamName == null) { + bitstreamName = response.headers().firstValue("Content-Disposition") + .filter(value -> value.contains("filename=")).flatMap(value -> Stream.of(value.split(";")) + .filter(v -> v.contains("filename=")) + .findFirst() + .map(fvalue -> fvalue.replaceFirst("filename=", "").replaceAll("\"", ""))) + .orElse(uri.getPath().substring(uri.getPath().lastIndexOf('/') + 1)); + } + + try (InputStream is = response.body()) { + saveFileToItem(context, item, is, bitstreamName); + } + + context.commit(); + } + + private Item findItem(Context context) throws SQLException { + if (itemUUID != null) { + return itemService.find(context, itemUUID); + } else if (workspaceID != 0) { + return workspaceItemService.find(context, workspaceID).getItem(); + } else { + try { + DSpaceObject dso = identifierService.resolve(context, pid); + if (dso instanceof Item) { + return (Item) dso; + } else { + throw new IllegalArgumentException("The provided identifier does not resolve to an item"); + } + } catch (IdentifierNotFoundException | IdentifierNotResolvableException e) { + throw new IllegalArgumentException(e); + } + } + } + + private void saveFileToItem(Context context, Item item, InputStream is, String name) + throws SQLException, AuthorizeException, IOException { + log.debug("Saving file to item {}", item.getID()); + List originals = item.getBundles("ORIGINAL"); + Bitstream b; + if (originals.isEmpty()) { + b = itemService.createSingleBitstream(context, is, item); + } else { + Bundle bundle = originals.get(0); + b = bitstreamService.create(context, bundle, is); + } + b.setName(context, name); + //now guess format of the bitstream + BitstreamFormat bf = bitstreamFormatService.guessFormat(context, b); + b.setFormat(context, bf); + } + + private EPerson getEperson(Context context) throws SQLException { + if (getEpersonIdentifier() != null) { + return epersonService.find(context, getEpersonIdentifier()); + } else { + return epersonService.findByEmail(context, epersonMail); + } + } +} + diff --git a/dspace-api/src/main/java/org/dspace/administer/FileDownloaderConfiguration.java b/dspace-api/src/main/java/org/dspace/administer/FileDownloaderConfiguration.java new file mode 100644 index 000000000000..848b2d99f7c0 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/administer/FileDownloaderConfiguration.java @@ -0,0 +1,73 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.administer; + +import org.apache.commons.cli.OptionGroup; +import org.apache.commons.cli.Options; +import org.dspace.scripts.configuration.ScriptConfiguration; + +public class FileDownloaderConfiguration extends ScriptConfiguration { + + private Class dspaceRunnableClass; + + /** + * Generic getter for the dspaceRunnableClass + * + * @return the dspaceRunnableClass value of this ScriptConfiguration + */ + @Override + public Class getDspaceRunnableClass() { + return dspaceRunnableClass; + } + + /** + * Generic setter for the dspaceRunnableClass + * + * @param dspaceRunnableClass The dspaceRunnableClass to be set on this IndexDiscoveryScriptConfiguration + */ + @Override + public void setDspaceRunnableClass(Class dspaceRunnableClass) { + this.dspaceRunnableClass = dspaceRunnableClass; + } + + /** + * The getter for the options of the Script + * + * @return the options value of this ScriptConfiguration + */ + @Override + public Options getOptions() { + if (options == null) { + + Options options = new Options(); + OptionGroup ids = new OptionGroup(); + + options.addOption("h", "help", false, "help"); + + options.addOption("u", "url", true, "source url"); + options.getOption("u").setRequired(true); + + options.addOption("i", "uuid", true, "item uuid"); + options.addOption("w", "wsid", true, "workspace id"); + options.addOption("p", "pid", true, "item pid (e.g. handle or doi)"); + ids.addOption(options.getOption("i")); + ids.addOption(options.getOption("w")); + ids.addOption(options.getOption("p")); + ids.setRequired(true); + + options.addOption("e", "eperson", true, "eperson email"); + options.getOption("e").setRequired(false); + + options.addOption("n", "name", true, "name of the file/bitstream"); + options.getOption("n").setRequired(false); + + super.options = options; + } + return options; + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/statistics/clarin/ClarinMatomoBitstreamTracker.java b/dspace-api/src/main/java/org/dspace/app/statistics/clarin/ClarinMatomoBitstreamTracker.java index 838a2650586a..5b90ab7fc740 100644 --- a/dspace-api/src/main/java/org/dspace/app/statistics/clarin/ClarinMatomoBitstreamTracker.java +++ b/dspace-api/src/main/java/org/dspace/app/statistics/clarin/ClarinMatomoBitstreamTracker.java @@ -9,6 +9,7 @@ import java.sql.SQLException; import java.text.MessageFormat; +import java.util.LinkedHashMap; import java.util.List; import java.util.Objects; import javax.servlet.http.HttpServletRequest; @@ -69,7 +70,6 @@ public ClarinMatomoBitstreamTracker() { @Override protected void preTrack(Context context, MatomoRequest matomoRequest, Item item, HttpServletRequest request) { super.preTrack(context, matomoRequest, item, request); - matomoRequest.setSiteId(siteId); log.debug("Logging to site " + matomoRequest.getSiteId()); String itemIdentifier = getItemIdentifier(item); @@ -82,6 +82,11 @@ protected void preTrack(Context context, MatomoRequest matomoRequest, Item item, } try { matomoRequest.setPageCustomVariable(new CustomVariable("source", "bitstream"), 1); + // Add the Item handle into the request as a custom dimension + LinkedHashMap handleDimension = new LinkedHashMap<>(); + handleDimension.put(configurationService.getLongProperty("matomo.custom.dimension.handle.id", + 1L), item.getHandle()); + matomoRequest.setDimensions(handleDimension); } catch (MatomoException e) { log.error(e); } diff --git a/dspace-api/src/main/java/org/dspace/app/statistics/clarin/ClarinMatomoTracker.java b/dspace-api/src/main/java/org/dspace/app/statistics/clarin/ClarinMatomoTracker.java index 360b4efe8e93..34615bc2ed24 100644 --- a/dspace-api/src/main/java/org/dspace/app/statistics/clarin/ClarinMatomoTracker.java +++ b/dspace-api/src/main/java/org/dspace/app/statistics/clarin/ClarinMatomoTracker.java @@ -7,14 +7,14 @@ */ package org.dspace.app.statistics.clarin; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.Calendar; import java.util.Objects; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpResponse; import org.apache.logging.log4j.Logger; import org.dspace.content.Item; import org.dspace.content.factory.ClarinServiceFactory; @@ -23,6 +23,7 @@ import org.dspace.services.factory.DSpaceServicesFactory; import org.matomo.java.tracking.MatomoException; import org.matomo.java.tracking.MatomoRequest; +import org.matomo.java.tracking.parameters.AcceptLanguage; /** * The statistics Tracker for Matomo. This class prepare and send the track GET request to the `/matomo.php` @@ -99,13 +100,13 @@ protected MatomoRequest createMatomoRequest(HttpServletRequest request, String p */ protected void preTrack(Context context, MatomoRequest matomoRequest, Item item, HttpServletRequest request) { if (StringUtils.isNotBlank(request.getHeader("referer"))) { - matomoRequest.setHeaderUserAgent(request.getHeader("referer")); + matomoRequest.setReferrerUrl(request.getHeader("referer")); } if (StringUtils.isNotBlank(request.getHeader("user-agent"))) { matomoRequest.setHeaderUserAgent(request.getHeader("user-agent")); } if (StringUtils.isNotBlank(request.getHeader("accept-language"))) { - matomoRequest.setHeaderUserAgent(request.getHeader("accept-language")); + matomoRequest.setHeaderAcceptLanguage(AcceptLanguage.fromHeader(request.getHeader("accept-language"))); } // Creating a calendar using getInstance method @@ -134,18 +135,13 @@ protected void preTrack(Context context, MatomoRequest matomoRequest, Item item, * @param matomoRequest prepared MatomoRequest for sending */ public void sendTrackingRequest(MatomoRequest matomoRequest) { - try { - Future response = tracker.sendRequestAsync(matomoRequest); - // usually not needed: - HttpResponse httpResponse = response.get(); - int statusCode = httpResponse.getStatusLine().getStatusCode(); - if (statusCode > 399) { - // problem - log.error("Matomo tracker error the response has status code: " + statusCode); + CompletableFuture completableFuture = tracker.sendRequestAsync(matomoRequest); + + completableFuture.whenComplete((result, exception) -> { + if (exception != null) { + log.error("Matomo tracker error - the response exception message: {}", exception.getMessage()); } - } catch (ExecutionException | InterruptedException e) { - e.printStackTrace(); - } + }); } protected String getFullURL(HttpServletRequest request) { @@ -164,21 +160,35 @@ protected String getFullURL(HttpServletRequest request) { } /** - * Get IpAddress of the current user which throws this statistic event + * Get IpAddress of the current user which throws this statistic event. Return only the first valid IPv4 address + * because the Matomo tracker has a problem with IPv6 addresses. * * @param request current request - * @return + * @return only the first valid IPv4 address */ protected String getIpAddress(HttpServletRequest request) { - String ip = ""; String header = request.getHeader("X-Forwarded-For"); if (header == null) { header = request.getRemoteAddr(); } if (header != null) { String[] ips = header.split(", "); - ip = ips.length > 0 ? ips[0] : ""; + for (String candidateIp : ips) { + // Validate if it's an IPv4 address + if (isIPv4Address(candidateIp)) { + return candidateIp; + } + } + } + return null; + } + + private boolean isIPv4Address(String ip) { + try { + InetAddress inetAddress = InetAddress.getByName(ip); + return inetAddress.getHostAddress().equals(ip) && inetAddress instanceof java.net.Inet4Address; + } catch (UnknownHostException e) { + return false; // Not a valid IP address } - return ip; } } diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java index 9fd306d1aaf4..2ea8a8866016 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInput.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInput.java @@ -25,6 +25,7 @@ import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.xml.sax.SAXException; @@ -163,6 +164,16 @@ public class DCInput { */ private ComplexDefinition complexDefinition = null; + /** + * give suggestions from this specific autocomplete solr index/file + */ + private String autocompleteCustom = null; + + /** + * the custom field for the type bind + */ + private String typeBindField = null; + /** * the dropdown input type could have defined a default value */ @@ -234,6 +245,9 @@ public DCInput(Map fieldMap, Map> listMap, if ("complex".equals(inputType)) { complexDefinition = complexDefinitions.getByName((fieldMap.get(DCInputsReader.COMPLEX_DEFINITION_REF))); } + if ("autocomplete".equals(inputType)) { + autocompleteCustom = fieldMap.get(DCInputsReader.AUTOCOMPLETE_CUSTOM); + } hint = fieldMap.get("hint"); warning = fieldMap.get("required"); required = warning != null && warning.length() > 0; @@ -249,12 +263,11 @@ public DCInput(Map fieldMap, Map> listMap, // parsing of the element (using the colon as split separator) typeBind = new ArrayList(); String typeBindDef = fieldMap.get("type-bind"); - if (typeBindDef != null && typeBindDef.trim().length() > 0) { - String[] types = typeBindDef.split(","); - for (String type : types) { - typeBind.add(type.trim()); - } - } + this.insertToTypeBind(typeBindDef); + typeBindField = fieldMap.get(DCInputsReader.TYPE_BIND_FIELD_ATTRIBUTE); + this.insertToTypeBind(typeBindField); + + style = fieldMap.get("style"); isRelationshipField = fieldMap.containsKey("relationship-type"); isMetadataField = fieldMap.containsKey("dc-schema"); @@ -273,6 +286,15 @@ public DCInput(Map fieldMap, Map> listMap, } + private void insertToTypeBind(String typeBindDef) { + if (StringUtils.isNotEmpty(typeBindDef)) { + String[] types = typeBindDef.split(","); + for (String type : types) { + typeBind.add(type.trim()); + } + } + } + protected void initRegex(String regex) { this.regex = null; this.pattern = null; @@ -559,6 +581,21 @@ public boolean isAllowedFor(String typeName) { return typeBind.contains(typeName); } + /** + * Decides if this field is valid for the document type + * Check if one of the typeName is in the typeBind list + * + * @param typeNames List of document type names e.g. ["VIDEO"] + * @return true when there is no type restriction or typeName is allowed + */ + public boolean isAllowedFor(List typeNames) { + if (typeBind.isEmpty()) { + return true; + } + + return CollectionUtils.containsAny(typeBind, typeNames); + } + public String getScope() { return visibility; } @@ -701,6 +738,22 @@ public boolean isMetadataField() { return isMetadataField; } + public String getAutocompleteCustom() { + return autocompleteCustom; + } + + public void setAutocompleteCustom(String autocompleteCustom) { + this.autocompleteCustom = autocompleteCustom; + } + + public String getTypeBindField() { + return typeBindField; + } + + public void setTypeBindField(String typeBindField) { + this.typeBindField = typeBindField; + } + /** * Class representing a Map of the ComplexDefinition object * Class is copied from UFAL/CLARIN-DSPACE (https://github.com/ufal/clarin-dspace) and modified by diff --git a/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java b/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java index d013a7d3fe7b..f327a647db3e 100644 --- a/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java +++ b/dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java @@ -74,6 +74,12 @@ public class DCInputsReader { * Keyname for storing the name of the complex input type */ static final String COMPLEX_DEFINITION_REF = "complex-definition-ref"; + public static final String TYPE_BIND_FIELD_ATTRIBUTE = "field"; + + /** + * Keyname for storing the name of the custom autocomplete input type + */ + static final String AUTOCOMPLETE_CUSTOM = "autocomplete-custom"; /** @@ -495,6 +501,11 @@ private void processField(String formName, Node n, Map field) handleInputTypeTagName(formName, field, nestedNode, nestedValue); } } + } else if (StringUtils.equals(tagName, "type-bind")) { + String customField = getAttribute(nd, TYPE_BIND_FIELD_ATTRIBUTE); + if (customField != null) { + field.put(TYPE_BIND_FIELD_ATTRIBUTE, customField); + } } } } @@ -541,15 +552,23 @@ private void handleInputTypeTagName(String formName, Map field, } else { field.put(PAIR_TYPE_NAME, pairTypeName); } - } else if (value.equals("complex")) { - String definitionName = getAttribute(nd, COMPLEX_DEFINITION_REF); - if (definitionName == null) { - throw new SAXException("Form " + formName - + ", field " + field.get("dc-element") - + "." + field.get("dc-qualifier") - + " has no linked definition"); - } else { - field.put(COMPLEX_DEFINITION_REF, definitionName); + } else { + if (value.equals("complex")) { + String definitionName = getAttribute(nd, COMPLEX_DEFINITION_REF); + if (definitionName == null) { + throw new SAXException("Form " + formName + + ", field " + field.get("dc-element") + + "." + field.get("dc-qualifier") + + " has no linked definition"); + } else { + field.put(COMPLEX_DEFINITION_REF, definitionName); + } + } + if (value.equals("autocomplete")) { + String definitionName = getAttribute(nd, AUTOCOMPLETE_CUSTOM); + if (definitionName != null) { + field.put(AUTOCOMPLETE_CUSTOM, definitionName); + } } } } diff --git a/dspace-api/src/main/java/org/dspace/authenticate/clarin/ClarinShibAuthentication.java b/dspace-api/src/main/java/org/dspace/authenticate/clarin/ClarinShibAuthentication.java index 822543d08c80..ba5d8cd65bfa 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/clarin/ClarinShibAuthentication.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/clarin/ClarinShibAuthentication.java @@ -11,6 +11,7 @@ import java.net.URLEncoder; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -19,13 +20,13 @@ import java.util.Objects; import java.util.UUID; import java.util.regex.Pattern; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.dspace.app.util.Util; import org.dspace.authenticate.AuthenticationMethod; import org.dspace.authenticate.factory.AuthenticateServiceFactory; import org.dspace.authorize.AuthorizeException; @@ -237,9 +238,18 @@ public int authenticate(Context context, String username, String password, // The user e-mail is not stored in the `shibheaders` but in the `clarinVerificationToken`. // The email was added to the `clarinVerificationToken` in the ClarinShibbolethFilter. - String netidHeader = configurationService.getProperty("authentication-shibboleth.netid-header"); - clarinVerificationToken = clarinVerificationTokenService.findByNetID(context, - shibheaders.get_single(netidHeader)); + String[] netidHeaders = configurationService.getArrayProperty("authentication-shibboleth.netid-header"); + + // Load the verification token from the request header or from the request parameter. + // This is only set if the user is trying to authenticate with the `verification-token`. + String VERIFICATION_TOKEN = "verification-token"; + String verificationTokenFromRequest = StringUtils.defaultIfBlank(request.getHeader(VERIFICATION_TOKEN), + request.getParameter(VERIFICATION_TOKEN)); + if (StringUtils.isNotEmpty(verificationTokenFromRequest)) { + log.info("Verification token from request header `{}`: {}", VERIFICATION_TOKEN, + verificationTokenFromRequest); + clarinVerificationToken = clarinVerificationTokenService.findByToken(context, verificationTokenFromRequest); + } // CLARIN // Initialize the additional EPerson metadata. @@ -251,11 +261,11 @@ public int authenticate(Context context, String username, String password, // Four steps to authenticate a user try { // Step 1: Identify User - EPerson eperson = findEPerson(context, request); + EPerson eperson = findEPerson(context, request, netidHeaders); // Step 2: Register New User, if necessary if (eperson == null && autoRegister && !isDuplicateUser) { - eperson = registerNewEPerson(context, request); + eperson = registerNewEPerson(context, request, netidHeaders); } if (eperson == null) { @@ -263,7 +273,7 @@ public int authenticate(Context context, String username, String password, } // Step 3: Update User's Metadata - updateEPerson(context, request, eperson); + updateEPerson(context, request, eperson, netidHeaders); // Step 4: Log the user in. context.setCurrentUser(eperson); @@ -540,11 +550,11 @@ public static boolean isEnabled() { * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - protected EPerson findEPerson(Context context, HttpServletRequest request) throws SQLException, AuthorizeException { + protected EPerson findEPerson(Context context, HttpServletRequest request, String[] netidHeaders) + throws SQLException { boolean isUsingTomcatUser = configurationService .getBooleanProperty("authentication-shibboleth.email-use-tomcat-remote-user"); - String netidHeader = configurationService.getProperty("authentication-shibboleth.netid-header"); String emailHeader = configurationService.getProperty("authentication-shibboleth.email-header"); EPerson eperson = null; @@ -554,26 +564,10 @@ protected EPerson findEPerson(Context context, HttpServletRequest request) throw // 1) First, look for a netid header. - if (netidHeader != null) { - String org = shibheaders.get_idp(); - String netid = Util.formatNetId(findSingleAttribute(request, netidHeader), org); - if (StringUtils.isEmpty(netid)) { - netid = shibheaders.get_single(netidHeader); - } - - if (netid != null) { + if (netidHeaders != null) { + eperson = findEpersonByNetId(netidHeaders, shibheaders, ePersonService, context, true); + if (eperson != null) { foundNetID = true; - eperson = ePersonService.findByNetid(context, netid); - - if (eperson == null) { - log.info( - "Unable to identify EPerson based upon Shibboleth netid header: '" + netidHeader + "'='" + - netid + "'."); - } else { - log.debug( - "Identified EPerson based upon Shibboleth netid header: '" + netidHeader + "'='" + - netid + "'" + "."); - } } } @@ -656,7 +650,6 @@ protected EPerson findEPerson(Context context, HttpServletRequest request) throw return eperson; } - /** * Register a new eperson object. This method is called when no existing user was * found for the NetID or Email and autoregister is enabled. When these conditions @@ -677,11 +670,10 @@ protected EPerson findEPerson(Context context, HttpServletRequest request) throw * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - protected EPerson registerNewEPerson(Context context, HttpServletRequest request) + protected EPerson registerNewEPerson(Context context, HttpServletRequest request, String[] netidHeaders) throws SQLException, AuthorizeException { // Header names - String netidHeader = configurationService.getProperty("authentication-shibboleth.netid-header"); String emailHeader = configurationService.getProperty("authentication-shibboleth.email-header"); String fnameHeader = configurationService.getProperty("authentication-shibboleth.firstname-header"); String lnameHeader = configurationService.getProperty("authentication-shibboleth.lastname-header"); @@ -694,15 +686,12 @@ protected EPerson registerNewEPerson(Context context, HttpServletRequest request // CLARIN // Header values - String netid = Util.formatNetId(findSingleAttribute(request, netidHeader), org); + String netid = getFirstNetId(netidHeaders); String email = getEmailAcceptedOrNull(findSingleAttribute(request, emailHeader)); String fname = Headers.updateValueByCharset(findSingleAttribute(request, fnameHeader)); String lname = Headers.updateValueByCharset(findSingleAttribute(request, lnameHeader)); // If the values are not in the request headers try to retrieve it from `shibheaders`. - if (StringUtils.isEmpty(netid)) { - netid = shibheaders.get_single(netidHeader); - } if (StringUtils.isEmpty(email) && Objects.nonNull(clarinVerificationToken)) { email = clarinVerificationToken.getEmail(); } @@ -718,7 +707,7 @@ protected EPerson registerNewEPerson(Context context, HttpServletRequest request // don't have at least these three pieces of information then we fail. String message = "Unable to register new eperson because we are unable to find an email address along " + "with first and last name for the user.\n"; - message += " NetId Header: '" + netidHeader + "'='" + netid + "' (Optional) \n"; + message += " NetId Header: '" + Arrays.toString(netidHeaders) + "'='" + netid + "' (Optional) \n"; message += " Email Header: '" + emailHeader + "'='" + email + "' \n"; message += " First Name Header: '" + fnameHeader + "'='" + fname + "' \n"; message += " Last Name Header: '" + lnameHeader + "'='" + lname + "'"; @@ -807,24 +796,20 @@ protected EPerson registerNewEPerson(Context context, HttpServletRequest request * @throws SQLException if database error * @throws AuthorizeException if authorization error */ - protected void updateEPerson(Context context, HttpServletRequest request, EPerson eperson) + protected void updateEPerson(Context context, HttpServletRequest request, EPerson eperson, String[] netidHeaders) throws SQLException, AuthorizeException { // Header names & values - String netidHeader = configurationService.getProperty("authentication-shibboleth.netid-header"); String emailHeader = configurationService.getProperty("authentication-shibboleth.email-header"); String fnameHeader = configurationService.getProperty("authentication-shibboleth.firstname-header"); String lnameHeader = configurationService.getProperty("authentication-shibboleth.lastname-header"); - String netid = Util.formatNetId(findSingleAttribute(request, netidHeader), shibheaders.get_idp()); + String netid = getFirstNetId(netidHeaders); String email = getEmailAcceptedOrNull(findSingleAttribute(request, emailHeader)); String fname = Headers.updateValueByCharset(findSingleAttribute(request, fnameHeader)); String lname = Headers.updateValueByCharset(findSingleAttribute(request, lnameHeader)); // If the values are not in the request headers try to retrieve it from `shibheaders`. - if (StringUtils.isEmpty(netid)) { - netid = shibheaders.get_single(netidHeader); - } if (StringUtils.isEmpty(email) && Objects.nonNull(clarinVerificationToken)) { email = clarinVerificationToken.getEmail(); } @@ -858,7 +843,16 @@ protected void updateEPerson(Context context, HttpServletRequest request, EPerso } // The email could have changed if using netid based lookup. if (email != null) { - eperson.setEmail(email.toLowerCase()); + String lowerCaseEmail = email.toLowerCase(); + // Check the email is unique + EPerson epersonByEmail = ePersonService.findByEmail(context, lowerCaseEmail); + if (epersonByEmail != null && !epersonByEmail.getID().equals(eperson.getID())) { + log.error("Unable to update the eperson's email metadata because the email '{}' is already in use.", + lowerCaseEmail); + throw new AuthorizeException("The email address is already in use."); + } else { + eperson.setEmail(email.toLowerCase()); + } } if (fname != null) { eperson.setFirstName(context, fname); @@ -1207,29 +1201,11 @@ public String findSingleAttribute(HttpServletRequest request, String name) { if (name == null) { return null; } - String value = findAttribute(request, name); - if (value != null) { - // If there are multiple values encoded in the shibboleth attribute - // they are separated by a semicolon, and any semicolons in the - // attribute are escaped with a backslash. For this case we are just - // looking for the first attribute so we scan the value until we find - // the first unescaped semicolon and chop off everything else. - int idx = 0; - do { - idx = value.indexOf(';', idx); - if (idx != -1 && value.charAt(idx - 1) != '\\') { - value = value.substring(0, idx); - break; - } - } while (idx >= 0); - - // Unescape the semicolon after splitting - value = value.replaceAll("\\;", ";"); + value = sortEmailsAndGetFirst(value); } - return value; } @@ -1338,5 +1314,71 @@ public String getEmailAcceptedOrNull(String email) { } return email; } + + /** + * Find an EPerson by a NetID header. The method will go through all the netid headers and try to find a user. + */ + public static EPerson findEpersonByNetId(String[] netidHeaders, ShibHeaders shibheaders, + EPersonService ePersonService, Context context, boolean logAllowed) + throws SQLException { + // Go through all the netid headers and try to find a user. It could be e.g., `eppn`, `persistent-id`,.. + for (String netidHeader : netidHeaders) { + netidHeader = netidHeader.trim(); + String netid = shibheaders.get_single(netidHeader); + if (netid == null) { + continue; + } + + EPerson eperson = ePersonService.findByNetid(context, netid); + + if (eperson == null && logAllowed) { + log.info( + "Unable to identify EPerson based upon Shibboleth netid header: '" + netidHeader + + "'='" + netid + "'."); + } else if (eperson != null) { + log.debug( + "Identified EPerson based upon Shibboleth netid header: '" + netidHeader + "'='" + + netid + "'" + "."); + return eperson; + } + } + return null; + } + + /** + * Sort the email addresses and return the first one. + * @param value The email addresses separated by semicolons. + */ + public static String sortEmailsAndGetFirst(String value) { + // If there are multiple values encoded in the shibboleth attribute + // they are separated by a semicolon, and any semicolons in the + // attribute are escaped with a backslash. + // Step 1: Split the input string into email addresses + List emails = Arrays.stream(value.split("(? email.replaceAll("\\\\;", ";")) // Unescape semicolons + .collect(Collectors.toList()); + + // Step 2: Sort the email list alphabetically + emails.sort(String::compareToIgnoreCase); + + // Step 3: Get the first sorted email + return emails.get(0); + } + + /** + * Get the first netid from the list of netid headers. E.g., eppn, persistent-id,... + * @param netidHeaders list of netid headers loaded from the configuration `authentication-shibboleth.netid-header` + */ + public String getFirstNetId(String[] netidHeaders) { + for (String netidHeader : netidHeaders) { + netidHeader = netidHeader.trim(); + String netid = shibheaders.get_single(netidHeader); + if (netid != null) { + //When creating use first match (eppn before targeted-id) + return netid; + } + } + return null; + } } diff --git a/dspace-api/src/main/java/org/dspace/authenticate/clarin/ShibHeaders.java b/dspace-api/src/main/java/org/dspace/authenticate/clarin/ShibHeaders.java index 65897087302e..a4b85e53be2e 100644 --- a/dspace-api/src/main/java/org/dspace/authenticate/clarin/ShibHeaders.java +++ b/dspace-api/src/main/java/org/dspace/authenticate/clarin/ShibHeaders.java @@ -14,6 +14,7 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -32,7 +33,7 @@ public class ShibHeaders { // constants // private static final String header_separator_ = ";"; - private String netIdHeader = ""; + private String[] netIdHeaders = null; ConfigurationService configurationService = DSpaceServicesFactory.getInstance().getConfigurationService(); // variables @@ -105,7 +106,7 @@ public String get_single(String name) { List values = get(name); if (values != null && !values.isEmpty()) { // Format netId - if (StringUtils.equals(name, this.netIdHeader)) { + if (ArrayUtils.contains(this.netIdHeaders, name)) { return Util.formatNetId(values.get(0), this.get_idp()); } return values.get(0); @@ -150,6 +151,10 @@ public void log_headers() { } private void initializeNetIdHeader() { - this.netIdHeader = configurationService.getProperty("authentication-shibboleth.netid-header"); + this.netIdHeaders = configurationService.getArrayProperty("authentication-shibboleth.netid-header"); + } + + public String[] getNetIdHeaders() { + return this.netIdHeaders; } } diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java index a4c880173bf7..77937e8ee76b 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java @@ -73,6 +73,9 @@ public class WorkspaceItem @Column(name = "page_reached") private Integer pageReached = -1; + @Column(name = "share_token") + private String shareToken = null; + /** * Protected constructor, create object using: * {@link org.dspace.content.service.WorkspaceItemService#create(Context, Collection, boolean)} @@ -131,6 +134,14 @@ public void setPageReached(int v) { pageReached = v; } + public String getShareToken() { + return shareToken; + } + + public void setShareToken(String shareToken) { + this.shareToken = shareToken; + } + /** * Decide if this WorkspaceItem is equal to another * diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java index b6e7372af184..f39ab6ea526e 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItemServiceImpl.java @@ -243,6 +243,11 @@ public WorkspaceItem findByItem(Context context, Item item) throws SQLException return workspaceItemDAO.findByItem(context, item); } + @Override + public List findByShareToken(Context context, String shareToken) throws SQLException { + return workspaceItemDAO.findByShareToken(context, shareToken); + } + @Override public List findAll(Context context) throws SQLException { return workspaceItemDAO.findAll(context); diff --git a/dspace-api/src/main/java/org/dspace/content/clarin/ClarinVerificationTokenServiceImpl.java b/dspace-api/src/main/java/org/dspace/content/clarin/ClarinVerificationTokenServiceImpl.java index 27b4ca7cad80..33eff897579b 100644 --- a/dspace-api/src/main/java/org/dspace/content/clarin/ClarinVerificationTokenServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/clarin/ClarinVerificationTokenServiceImpl.java @@ -12,6 +12,7 @@ import java.util.Objects; import org.apache.commons.lang.NullArgumentException; +import org.dspace.authenticate.clarin.ShibHeaders; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.service.AuthorizeService; import org.dspace.content.dao.clarin.ClarinVerificationTokenDAO; @@ -75,6 +76,19 @@ public ClarinVerificationToken findByNetID(Context context, String netID) throws return clarinVerificationTokenDAO.findByNetID(context, netID); } + @Override + public ClarinVerificationToken findByNetID(Context context, String[] netIdHeaders, ShibHeaders shibHeaders) + throws SQLException { + for (String netidHeader : netIdHeaders) { + String netID = shibHeaders.get_single(netidHeader); + ClarinVerificationToken clarinVerificationToken = clarinVerificationTokenDAO.findByNetID(context, netID); + if (Objects.nonNull(clarinVerificationToken)) { + return clarinVerificationToken; + } + } + return null; + } + @Override public void delete(Context context, ClarinVerificationToken clarinVerificationToken) throws SQLException { diff --git a/dspace-api/src/main/java/org/dspace/content/dao/WorkspaceItemDAO.java b/dspace-api/src/main/java/org/dspace/content/dao/WorkspaceItemDAO.java index 900858b72869..6996d6ce4010 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/WorkspaceItemDAO.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/WorkspaceItemDAO.java @@ -37,6 +37,8 @@ public List findByEPerson(Context context, EPerson ep, Integer li public WorkspaceItem findByItem(Context context, Item i) throws SQLException; + public List findByShareToken(Context context, String shareToken) throws SQLException; + public List findAll(Context context) throws SQLException; public List findAll(Context context, Integer limit, Integer offset) throws SQLException; diff --git a/dspace-api/src/main/java/org/dspace/content/dao/impl/WorkspaceItemDAOImpl.java b/dspace-api/src/main/java/org/dspace/content/dao/impl/WorkspaceItemDAOImpl.java index 138451365522..43b127e7c939 100644 --- a/dspace-api/src/main/java/org/dspace/content/dao/impl/WorkspaceItemDAOImpl.java +++ b/dspace-api/src/main/java/org/dspace/content/dao/impl/WorkspaceItemDAOImpl.java @@ -81,6 +81,14 @@ public WorkspaceItem findByItem(Context context, Item i) throws SQLException { return uniqueResult(context, criteriaQuery, false, WorkspaceItem.class); } + @Override + public List findByShareToken(Context context, String shareToken) throws SQLException { + Query query = createQuery(context, + "from WorkspaceItem ws where ws.shareToken = :shareToken"); + query.setParameter("shareToken", shareToken); + return list(query); + } + @Override public List findAll(Context context) throws SQLException { CriteriaBuilder criteriaBuilder = getCriteriaBuilder(context); diff --git a/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java b/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java index c8df68e43498..c4e9c54575a1 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/WorkspaceItemService.java @@ -127,6 +127,16 @@ public List findByCollection(Context context, Collection collecti public WorkspaceItem findByItem(Context context, Item item) throws SQLException; + /** + * Find the workspace item by the share token. + * @param context the DSpace context object + * @param shareToken the share token + * @return the List of workspace items or null + * @throws SQLException if database error + */ + public List findByShareToken(Context context, String shareToken) + throws SQLException; + /** * Get all workspace items in the whole system * diff --git a/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinVerificationTokenService.java b/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinVerificationTokenService.java index ec9d23cde5a0..653a765929f1 100644 --- a/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinVerificationTokenService.java +++ b/dspace-api/src/main/java/org/dspace/content/service/clarin/ClarinVerificationTokenService.java @@ -10,6 +10,7 @@ import java.sql.SQLException; import java.util.List; +import org.dspace.authenticate.clarin.ShibHeaders; import org.dspace.authorize.AuthorizeException; import org.dspace.content.clarin.ClarinVerificationToken; import org.dspace.core.Context; @@ -69,6 +70,18 @@ public interface ClarinVerificationTokenService { */ ClarinVerificationToken findByNetID(Context context, String netID) throws SQLException; + /** + * Find the clarin verification token object from the shibboleth headers trying every netId header + * until the object is found + * @param context DSpace context object + * @param netIdHeaders array of the netId headers - values from the configuration + * @param shibHeaders object with the shibboleth headers + * @return found clarin verification token object or null + * @throws SQLException if database error + */ + ClarinVerificationToken findByNetID(Context context, String[] netIdHeaders, ShibHeaders shibHeaders) + throws SQLException; + /** * Remove the clarin verification token from DB * @param context DSpace context object diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index 02a3fee09f8a..8eed24348c39 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -976,4 +976,16 @@ public Group getAdminGroup() throws SQLException { .getGroupService() .findByName(this, Group.ADMIN) : adminGroup; } + + /** + * Get the Hibernate statistics for this context. + * Only available when using HibernateDBConnection. + * @return the Hibernate statistics as a String + */ + public String getHibernateStatistics() { + if (dbConnection instanceof HibernateDBConnection) { + return ((HibernateDBConnection) dbConnection).getHibernateStatistics(); + } + return "Hibernate statistics are not available for this database connection"; + } } diff --git a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java index b371af80eede..f8c620380d5f 100644 --- a/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java +++ b/dspace-api/src/main/java/org/dspace/core/HibernateDBConnection.java @@ -12,6 +12,8 @@ import java.sql.SQLException; import javax.sql.DataSource; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dspace.authorize.ResourcePolicy; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; @@ -29,9 +31,11 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.proxy.HibernateProxyHelper; import org.hibernate.resource.transaction.spi.TransactionStatus; +import org.hibernate.stat.Statistics; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.orm.hibernate5.SessionFactoryUtils; +import org.springframework.scheduling.annotation.Scheduled; /** * Hibernate implementation of the DBConnection. @@ -64,6 +68,8 @@ public class HibernateDBConnection implements DBConnection { private boolean batchModeEnabled = false; private boolean readOnlyEnabled = false; + private static final Logger log = LogManager.getLogger(HibernateDBConnection.class); + /** * Retrieves the current Session from Hibernate (per our settings, Hibernate is configured to create one Session * per thread). If Session doesn't yet exist, it is created. A Transaction is also initialized (or reinintialized) @@ -102,6 +108,13 @@ protected Transaction getTransaction() { return sessionFactory.getCurrentSession().getTransaction(); } + // This method will run every 10 seconds + @Scheduled(fixedRate = 10000) // Fixed rate in milliseconds + public void logConnectionMetrics() { + logHibernateStatistics(); + logDatabaseMetaData(); + } + /** * Check if Hibernate Session is still "alive" / open. An open Session may or may not have an open Transaction * (so isTransactionAlive() may return false even if isSessionAlive() returns true). A Session may be reused for @@ -350,4 +363,53 @@ public void flushSession() throws SQLException { getSession().flush(); } } + + + /** + * Log the Hibernate statistics (e.g. open sessions, closed sessions, transactions, connections obtained) + */ + private void logHibernateStatistics() { + if (sessionFactory != null) { + log.info(getHibernateStatistics()); + } else { + log.warn(getHibernateStatistics()); + } + } + + /** + * Log the database metadata (URL, User, Driver, Product, Version) + */ + private void logDatabaseMetaData() { + try (Session session = sessionFactory.openSession()) { + // Use doReturningWork to safely interact with the JDBC Connection + session.doReturningWork(connection -> { + try { + DatabaseMetaData metaData = connection.getMetaData(); + log.info("Database Metadata - URL: {}, User: {}, Driver: {}, Product: {} {}" + , metaData.getURL(), metaData.getUserName(), metaData.getDriverName(), + metaData.getDatabaseProductName(), metaData.getDatabaseProductVersion()); + } catch (SQLException e) { + log.warn("Failed to retrieve database metadata: {}", e.getMessage()); + } + return null; // Returning null as no specific result is needed + }); + } catch (Exception e) { + log.warn("Failed to log database metadata: {}", e.getMessage()); + } + } + + /** + * Get Hibernate statistics as a string + */ + public String getHibernateStatistics() { + if (sessionFactory != null) { + Statistics stats = sessionFactory.getStatistics(); + return "Hibernate Statistics - Open Sessions: " + stats.getSessionOpenCount() + ", Closed Sessions: " + + stats.getSessionCloseCount() + ", Transactions: " + stats.getTransactionCount() + + ", Connections Obtained: " + stats.getConnectCount(); + } else { + return "SessionFactory is not available for logging Hibernate statistics."; + } + } } + diff --git a/dspace-api/src/main/java/org/dspace/core/Utils.java b/dspace-api/src/main/java/org/dspace/core/Utils.java index ea9ed57eca04..6831f45b5c51 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -506,4 +506,21 @@ public static String interpolateConfigsInString(String string) { ConfigurationService config = DSpaceServicesFactory.getInstance().getConfigurationService(); return StringSubstitutor.replace(string, config.getProperties()); } + + /** + * Replace the last occurrence of a substring within a string. + * + * @param input The input string + * @param toReplace The substring to replace + * @param replacement The replacement substring + * @return Replaced input string or the original input string if the substring to replace is not found + */ + public static String replaceLast(String input, String toReplace, String replacement) { + int lastIndex = input.lastIndexOf(toReplace); + if (lastIndex == -1) { + return input; // No replacement if not found + } + + return input.substring(0, lastIndex) + replacement + input.substring(lastIndex + toReplace.length()); + } } diff --git a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java index 5357e899702c..3a034cf71586 100644 --- a/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java +++ b/dspace-api/src/main/java/org/dspace/discovery/indexobject/ItemIndexFactoryImpl.java @@ -605,6 +605,28 @@ public void addDiscoveryFields(SolrInputDocument doc, Context context, Item item } + + // process item metadata + // just add _comp to local* + List mds = itemService.getMetadata(item, "local", Item.ANY, Item.ANY, Item.ANY); + for (MetadataValue meta : mds) { + String field = "local" + "." + meta.getMetadataField().getElement(); + String value = meta.getValue(); + if (value == null) { + continue; + } + String qualifier = meta.getMetadataField().getQualifier(); + if (qualifier != null && !qualifier.isEmpty()) { + field += "." + qualifier; + } + doc.addField(field + "_comp", value); + } + + // create handle_title_ac field + String title = item.getName(); + String handle = item.getHandle(); + doc.addField("handle_title_ac", handle + ":" + title); + log.debug(" Added Metadata"); try { diff --git a/dspace-api/src/main/java/org/dspace/handle/HandlePlugin.java b/dspace-api/src/main/java/org/dspace/handle/HandlePlugin.java index 1275ef81d695..a22736c179bb 100644 --- a/dspace-api/src/main/java/org/dspace/handle/HandlePlugin.java +++ b/dspace-api/src/main/java/org/dspace/handle/HandlePlugin.java @@ -254,6 +254,54 @@ public void scanNAs(ScanCallback callback) throws HandleException { // Resolving methods //////////////////////////////////////// + /** + * Resolve the given handle to DSpace object. + * + * @param context the context + * @param handle the handle to resolve + * @return the resolved DSpaceObject + * @throws HandleException if an error occurs during resolution + */ + private static DSpaceObject resolveHandleToObject(Context context, String handle) throws HandleException { + try { + return handleClarinService.resolveToObject(context, handle); + } catch (Exception e) { + if (log.isDebugEnabled()) { + log.debug("Exception in resolveHandleToObject", e); + } + throw new HandleException(HandleException.INTERNAL_ERROR); + } + } + + /** + * Retrieves handle values as a map. + * + * @param handle the handle to resolve + * @return a map containing the handle values + * @throws HandleException if an error occurs during handle resolution + */ + public static Map getMapHandleValues(String handle) throws HandleException { + if (log.isInfoEnabled()) { + log.info("Called getMapHandleValues"); + } + loadServices(); + Context context = new Context(); + try { + DSpaceObject dso = null; + boolean resolveMetadata = configurationService.getBooleanProperty("lr.pid.resolvemetadata", true); + if (resolveMetadata) { + dso = resolveHandleToObject(context, handle); + } + return extractMetadata(dso); + } finally { + try { + context.complete(); + } catch (SQLException sqle) { + // ignore + } + } + } + /** * Return the raw values for this handle. This implementation returns a * single URL value. @@ -285,15 +333,7 @@ public byte[][] getRawHandleValues(byte[] theHandle, int[] indexList, String handle = Util.decodeString(theHandle); context = new Context(); - - DSpaceObject dso = null; String url = handleClarinService.resolveToURL(context, handle); - - boolean resolveMetadata = configurationService.getBooleanProperty("lr.pid.resolvemetadata", true); - if (resolveMetadata) { - dso = handleClarinService.resolveToObject(context, handle); - } - if (Objects.isNull(url)) { // try with old prefix @@ -332,6 +372,11 @@ public byte[][] getRawHandleValues(byte[] theHandle, int[] indexList, rh = new ResolvedHandle(url, splits[1], splits[2], splits[3], splits[4], splits[5], splits[6], splits[7]); } else { + DSpaceObject dso = null; + boolean resolveMetadata = configurationService.getBooleanProperty("lr.pid.resolvemetadata", true); + if (resolveMetadata) { + dso = resolveHandleToObject(context, handle); + } rh = new ResolvedHandle(url, dso); } log.info(String.format("Handle [%s] resolved to [%s]", handle, url)); diff --git a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java index 57136d6143bb..62e8e46a49dd 100644 --- a/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java +++ b/dspace-api/src/main/java/org/dspace/identifier/doi/DataCiteConnector.java @@ -438,10 +438,11 @@ public void reserveDOI(Context context, DSpaceObject dso, String doi) return; } // 400 -> invalid XML + case (422): case (400): { log.warn("DataCite was unable to understand the XML we send."); log.warn("DataCite Metadata API returned a http status code " - + "400: " + resp.getContent()); + + resp.getStatusCode() + ": " + resp.getContent()); Format format = Format.getCompactFormat(); format.setEncoding("UTF-8"); XMLOutputter xout = new XMLOutputter(format); diff --git a/dspace-api/src/main/resources/iso_langs.json b/dspace-api/src/main/resources/iso_langs.json new file mode 100644 index 000000000000..35f95c150136 --- /dev/null +++ b/dspace-api/src/main/resources/iso_langs.json @@ -0,0 +1,7895 @@ +{ +"Ghotuo": "aaa", +"Alumu-Tesu": "aab", +"Ari": "aac", +"Amal": "aad", +"Arbëreshë Albanian": "aae", +"Aranadan": "aaf", +"Ambrak": "aag", +"Abu'Arapesh": "aah", +"Arifama-Miniafia": "aai", +"Ankave": "aak", +"Afade": "aal", +"Anambé": "aan", +"AlgerianSaharan Arabic": "aao", +"ParáArára": "aap", +"EasternAbnaki": "aaq", +"Afar": "aar", +"Aasáx": "aas", +"Arvanitika Albanian": "aat", +"Abau": "aau", +"Solong": "aaw", +"MandoboAtas": "aax", +"Amarasi": "aaz", +"Abé": "aba", +"Bankon": "abb", +"Ambala Ayta": "abc", +"Manide": "abd", +"Western Abnaki": "abe", +"Abai Sungai": "abf", +"Abaga": "abg", +"Tajiki Arabic": "abh", +"Abidji": "abi", +"Aka-Bea": "abj", +"Abkhazian": "abk", +"LampungNyo": "abl", +"Abanyom": "abm", +"Abua": "abn", +"Abon": "abo", +"AbellenAyta": "abp", +"Abaza": "abq", +"Abron": "abr", +"AmboneseMalay": "abs", +"Ambulas": "abt", +"Abure": "abu", +"BaharnaArabic": "abv", +"Pal": "abw", +"Inabaknon": "abx", +"AnemeWake": "aby", +"Abui": "abz", +"Achagua": "aca", +"Áncá": "acb", +"Gikyode": "acd", +"Achinese": "ace", +"Saint Lucian Creole French": "acf", +"Acoli": "ach", +"Aka-Cari": "aci", +"Aka-Kora": "ack", +"Akar-Bale": "acl", +"Mesopotamian Arabic": "acm", +"Achang": "acn", +"EasternAcipa": "acp", +"Ta'izzi-AdeniArabic": "acq", +"Achi": "acr", +"Acroá": "acs", +"Achterhoeks": "act", +"Achuar-Shiwiar": "acu", +"Achumawi": "acv", +"HijaziArabic": "acw", +"OmaniArabic": "acx", +"CypriotArabic": "acy", +"Acheron": "acz", +"Adangme": "ada", +"Adabe": "adb", +"Dzodinka": "add", +"Adele": "ade", +"DhofariArabic": "adf", +"Andegerebinha": "adg", +"Adhola": "adh", +"Adi": "adi", +"Adioukrou": "adj", +"Galo": "adl", +"Adang": "adn", +"Abu": "ado", +"Adangbe": "adq", +"Adonara": "adr", +"AdamorobeSign Language": "ads", +"Adnyamathanha": "adt", +"Aduge": "adu", +"Amundava": "adw", +"AmdoTibetan": "adx", +"Adyghe": "ady", +"Adzera": "adz", +"Areba": "aea", +"TunisianArabic": "aeb", +"SaidiArabic": "aec", +"ArgentineSign Language": "aed", +"Northeast Pashayi": "aee", +"Haeke": "aek", +"Ambele": "ael", +"Arem": "aem", +"ArmenianSign Language": "aen", +"Aer": "aeq", +"EasternArrernte": "aer", +"Alsea": "aes", +"Akeu": "aeu", +"Ambakich": "aew", +"Amele": "aey", +"Aeka": "aez", +"GulfArabic": "afb", +"Andai": "afd", +"Putukwam": "afe", +"AfghanSign Language": "afg", +"Afrihili": "afh", +"Akrukay": "afi", +"Nanubae": "afk", +"Defaka": "afn", +"Eloyi": "afo", +"Tapei": "afp", +"Afrikaans": "afr", +"Afro-SeminoleCreole": "afs", +"Afitti": "aft", +"Awutu": "afu", +"Obokuitai": "afz", +"Aguano": "aga", +"Legbo": "agb", +"Agatu": "agc", +"Agarabi": "agd", +"Angal": "age", +"Arguni": "agf", +"Angor": "agg", +"Ngelima": "agh", +"Agariya": "agi", +"Argobba": "agj", +"IsarogAgta": "agk", +"Fembe": "agl", +"Angaataha": "agm", +"Agutaynen": "agn", +"Tainae": "ago", +"Aghem": "agq", +"Aguaruna": "agr", +"Esimbi": "ags", +"Central Cagayan Agta": "agt", +"Aguacateco": "agu", +"Remontado Dumagat": "agv", +"Kahua": "agw", +"Aghul": "agx", +"SouthernAlta": "agy", +"Mt. Iriga Agta": "agz", +"Ahanta": "aha", +"Axamb": "ahb", +"Qimant": "ahg", +"Aghu": "ahh", +"TiagbamrinAizi": "ahi", +"Akha": "ahk", +"Igo": "ahl", +"MobumrinAizi": "ahm", +"Àhàn": "ahn", +"Ahom": "aho", +"AproumuAizi": "ahp", +"Ahirani": "ahr", +"Ashe": "ahs", +"Ahtena": "aht", +"Arosi": "aia", +"Ainu(China)": "aib", +"Ainbai": "aic", +"Alngith": "aid", +"Amara": "aie", +"Agi": "aif", +"Antigua and Barbuda Creole English": "aig", +"Ai-Cham": "aih", +"AssyrianNeo-Aramaic": "aii", +"LishanidNoshan": "aij", +"Ake": "aik", +"Aimele": "ail", +"Aimol": "aim", +"Ainu(Japan)": "ain", +"Aiton": "aio", +"Burumakok": "aip", +"Aimaq": "aiq", +"Airoran": "air", +"Arikem": "ait", +"Aari": "aiw", +"Aighon": "aix", +"Ali": "aiy", +"Aja(Sudan)": "aja", +"Aja(Benin)": "ajg", +"Ajië": "aji", +"Andajin": "ajn", +"South Levantine Arabic": "ajp", +"Judeo-TunisianArabic": "ajt", +"Judeo-Moroccan Arabic": "aju", +"Ajawa": "ajw", +"AmriKarbi": "ajz", +"Akan": "aka", +"BatakAngkola": "akb", +"Mpur": "akc", +"Ukpet-Ehom": "akd", +"Akawaio": "ake", +"Akpa": "akf", +"Anakalangu": "akg", +"AngalHeneng": "akh", +"Aiome": "aki", +"Aka-Jeru": "akj", +"Akkadian": "akk", +"Aklanon": "akl", +"Aka-Bo": "akm", +"Akurio": "ako", +"Siwu": "akp", +"Ak": "akq", +"Araki": "akr", +"Akaselem": "aks", +"Akolet": "akt", +"Akum": "aku", +"Akhvakh": "akv", +"Akwa": "akw", +"Aka-Kede": "akx", +"Aka-Kol": "aky", +"Alabama": "akz", +"Alago": "ala", +"Qawasqar": "alc", +"Alladian": "ald", +"Aleut": "ale", +"Alege": "alf", +"Alawa": "alh", +"Amaimon": "ali", +"Alangan": "alj", +"Alak": "alk", +"Allar": "all", +"Amblong": "alm", +"GhegAlbanian": "aln", +"Larike-Wakasihu": "alo", +"Alune": "alp", +"Algonquin": "alq", +"Alutor": "alr", +"ToskAlbanian": "als", +"Southern Altai": "alt", +"'Are'are": "alu", +"Alaba-K’abeena": "alw", +"Amol": "alx", +"Alyawarr": "aly", +"Alur": "alz", +"Amanayé": "ama", +"Ambo": "amb", +"Amahuaca": "amc", +"Yanesha'": "ame", +"Hamer-Banna": "amf", +"Amarag": "amg", +"Amharic": "amh", +"Amis": "ami", +"Amdang": "amj", +"Ambai": "amk", +"War-Jaintia": "aml", +"Ama (Papua New Guinea)": "amm", +"Amanab": "amn", +"Amo": "amo", +"Alamblak": "amp", +"Amahai": "amq", +"Amarakaeri": "amr", +"SouthernAmami-Oshima": "ams", +"Amto": "amt", +"Guerrero Amuzgo": "amu", +"Ambelau": "amv", +"WesternNeo-Aramaic": "amw", +"Anmatyerre": "amx", +"Ami": "amy", +"Atampaya": "amz", +"Andaqui": "ana", +"Andoa": "anb", +"Ngas": "anc", +"Ansus": "and", +"Xârâcùù": "ane", +"Animere": "anf", +"Old English (ca. 450-1100)": "ang", +"Nend": "anh", +"Andi": "ani", +"Anor": "anj", +"Goemai": "ank", +"Anu": "anl", +"Anal": "anm", +"Obolo": "ann", +"Andoque": "ano", +"Angika": "anp", +"Jarawa (India)": "anq", +"Andh": "anr", +"Anserma": "ans", +"Antakarinya": "ant", +"Anuak": "anu", +"Denya": "anv", +"Anaang": "anw", +"Andra-Hus": "anx", +"Anyin": "any", +"Anem": "anz", +"Angolar": "aoa", +"Abom": "aob", +"Pemon": "aoc", +"Andarum": "aod", +"AngalEnen": "aoe", +"Bragat": "aof", +"Angoram": "aog", +"Anindilyakwa": "aoi", +"Mufian": "aoj", +"Arhö": "aok", +"Alor": "aol", +"Ömie": "aom", +"BumbitaArapesh": "aon", +"Aore": "aor", +"Taikat": "aos", +"A'tong": "aot", +"A'ou": "aou", +"Atorada": "aox", +"UabMeto": "aoz", +"Sa'a": "apb", +"North Levantine Arabic": "apc", +"SudaneseArabic": "apd", +"Bukiyip": "ape", +"PahananAgta": "apf", +"Ampanang": "apg", +"Athpariya": "aph", +"Apiaká": "api", +"Jicarilla Apache": "apj", +"KiowaApache": "apk", +"LipanApache": "apl", +"Mescalero-ChiricahuaApache": "apm", +"Apinayé": "apn", +"Apalik": "apo", +"Apma": "app", +"A-Pucikwar": "apq", +"Arop-Lokep": "apr", +"Arop-Sissano": "aps", +"Apatani": "apt", +"Apurinã": "apu", +"Alapmunte": "apv", +"WesternApache": "apw", +"Aputai": "apx", +"Apalaí": "apy", +"Safeyoka": "apz", +"Archi": "aqc", +"Ampari Dogon": "aqd", +"Arigidi": "aqg", +"Aninka": "aqk", +"Atohwaim": "aqm", +"Northern Alta": "aqn", +"Atakapa": "aqp", +"Arhâ": "aqr", +"Angaité": "aqt", +"Akuntsu": "aqz", +"Arabic": "ara", +"StandardArabic": "arb", +"Official Aramaic (700-300 BCE)": "arc", +"Arabana": "ard", +"WesternArrarnta": "are", +"Aragonese": "arg", +"Arhuaco": "arh", +"Arikara": "ari", +"Arapaso": "arj", +"Arikapú": "ark", +"Arabela": "arl", +"Mapudungun": "arn", +"Araona": "aro", +"Arapaho": "arp", +"AlgerianArabic": "arq", +"Karo(Brazil)": "arr", +"NajdiArabic": "ars", +"Aruá (Amazonas State)": "aru", +"Arbore": "arv", +"Arawak": "arw", +"Aruá(Rodonia State)": "arx", +"MoroccanArabic": "ary", +"Egyptian Arabic": "arz", +"Asu(Tanzania)": "asa", +"Assiniboine": "asb", +"Casuarina Coast Asmat": "asc", +"AmericanSign Language": "ase", +"Australian Sign Language": "asf", +"Cishingini": "asg", +"Abishira": "ash", +"Buruwai": "asi", +"Nsari": "asj", +"Ashkun": "ask", +"Asilulu": "asl", +"Assamese": "asm", +"Xingú Asuriní": "asn", +"Dano": "aso", +"Algerian Sign Language": "asp", +"AustrianSign Language": "asq", +"Asuri": "asr", +"Ipulo": "ass", +"Asturian": "ast", +"TocantinsAsurini": "asu", +"Asoa": "asv", +"Australian Aborigines Sign Language": "asw", +"Muratayak": "asx", +"YaosakorAsmat": "asy", +"As": "asz", +"Pele-Ata": "ata", +"Zaiwa": "atb", +"Atsahuaca": "atc", +"AtaManobo": "atd", +"Atemble": "ate", +"Ivbie North-Okpela-Arhe": "atg", +"Attié": "ati", +"Atikamekw": "atj", +"Ati": "atk", +"Mt.Iraya Agta": "atl", +"Ata": "atm", +"Ashtiani": "atn", +"Atong": "ato", +"PudtolAtta": "atp", +"Aralle-Tabulahan": "atq", +"Waimiri-Atroari": "atr", +"GrosVentre": "ats", +"PamplonaAtta": "att", +"Reel": "atu", +"NorthernAltai": "atv", +"Atsugewi": "atw", +"Arutani": "atx", +"Aneityum": "aty", +"Arta": "atz", +"Asumboa": "aua", +"Alugu": "aub", +"Waorani": "auc", +"Anuta": "aud", +"Aguna": "aug", +"Aushi": "auh", +"Anuki": "aui", +"Awjilah": "auj", +"Heyo": "auk", +"Aulua": "aul", +"Asu(Nigeria)": "aum", +"MolmoOne": "aun", +"Auyokawa": "auo", +"Makayam": "aup", +"Anus": "auq", +"Aruek": "aur", +"Austral": "aut", +"Auye": "auu", +"Awyi": "auw", +"Aurá": "aux", +"Awiyaana": "auy", +"UzbekiArabic": "auz", +"Avaric": "ava", +"Avau": "avb", +"Alviri-Vidari": "avd", +"Avestan": "ave", +"Avikam": "avi", +"Kotava": "avk", +"Eastern Egyptian Bedawi Arabic": "avl", +"Angkamuthi": "avm", +"Avatime": "avn", +"Agavotaguerra": "avo", +"Aushiri": "avs", +"Au": "avt", +"Avokaya": "avu", +"Avá-Canoeiro": "avv", +"Awadhi": "awa", +"Awa (Papua New Guinea)": "awb", +"Cicipu": "awc", +"Awetí": "awe", +"Anguthimri": "awg", +"Awbono": "awh", +"Aekyom": "awi", +"Awabakal": "awk", +"Arawum": "awm", +"Awngi": "awn", +"Awak": "awo", +"Awera": "awr", +"South Awyu": "aws", +"Araweté": "awt", +"CentralAwyu": "awu", +"JairAwyu": "awv", +"Awun": "aww", +"Awara": "awx", +"EderaAwyu": "awy", +"Abipon": "axb", +"Ayerrerenge": "axe", +"MatoGrosso Arára": "axg", +"Yaka(Central African Republic)": "axk", +"Lower Southern Aranda": "axl", +"MiddleArmenian": "axm", +"Xaragure": "axx", +"Awar": "aya", +"AyizoGbe": "ayb", +"Southern Aymara": "ayc", +"Ayabadhu": "ayd", +"Ayere": "aye", +"Ginyanga": "ayg", +"HadramiArabic": "ayh", +"Leyigha": "ayi", +"Akuku": "ayk", +"LibyanArabic": "ayl", +"Aymara": "aym", +"Sanaani Arabic": "ayn", +"Ayoreo": "ayo", +"North Mesopotamian Arabic": "ayp", +"Ayi(Papua New Guinea)": "ayq", +"Central Aymara": "ayr", +"SorsogonAyta": "ays", +"Magbukun Ayta": "ayt", +"Ayu": "ayu", +"MaiBrat": "ayz", +"Azha": "aza", +"SouthAzerbaijani": "azb", +"Eastern Durango Nahuatl": "azd", +"Azerbaijani": "aze", +"San Pedro Amuzgos Amuzgo": "azg", +"NorthAzerbaijani": "azj", +"Ipalapa Amuzgo": "azm", +"Western Durango Nahuatl": "azn", +"Awing": "azo", +"FaireAtta": "azt", +"HighlandPuebla Nahuatl": "azz", +"Babatana": "baa", +"Bainouk-Gunyuño": "bab", +"Badui": "bac", +"Baré": "bae", +"Nubaca": "baf", +"Tuki": "bag", +"BahamasCreole English": "bah", +"Barakai": "baj", +"Bashkir": "bak", +"Baluchi": "bal", +"Bambara": "bam", +"Balinese": "ban", +"Waimaha": "bao", +"Bantawa": "bap", +"Bavarian": "bar", +"Basa (Cameroon)": "bas", +"Bada (Nigeria)": "bau", +"Vengo": "bav", +"Bambili-Bambui": "baw", +"Bamun": "bax", +"Batuley": "bay", +"Baatonum": "bba", +"Barai": "bbb", +"Batak Toba": "bbc", +"Bau": "bbd", +"Bangba": "bbe", +"Baibai": "bbf", +"Barama": "bbg", +"Bugan": "bbh", +"Barombi": "bbi", +"Ghomálá'": "bbj", +"Babanki": "bbk", +"Bats": "bbl", +"Babango": "bbm", +"Uneapa": "bbn", +"Northern Bobo Madaré": "bbo", +"WestCentral Banda": "bbp", +"Bamali": "bbq", +"Girawa": "bbr", +"Bakpinka": "bbs", +"Mburku": "bbt", +"Kulung(Nigeria)": "bbu", +"Karnai": "bbv", +"Baba": "bbw", +"Bubia": "bbx", +"Befang": "bby", +"CentralBai": "bca", +"Bainouk-Samik": "bcb", +"SouthernBalochi": "bcc", +"North Babar": "bcd", +"Bamenyam": "bce", +"Bamu": "bcf", +"BagaBinari": "bcg", +"Bariai": "bch", +"Baoulé": "bci", +"Bardi": "bcj", +"Bunaba": "bck", +"Central Bicolano": "bcl", +"Bannoni": "bcm", +"Bali(Nigeria)": "bcn", +"Kaluli": "bco", +"Bali (Democratic Republic of Congo)": "bcp", +"Bench": "bcq", +"Babine": "bcr", +"Kohumono": "bcs", +"Bendi": "bct", +"Awad Bing": "bcu", +"Shoo-Minda-Nye": "bcv", +"Bana": "bcw", +"Bacama": "bcy", +"Bainouk-Gunyaamolo": "bcz", +"Bayot": "bda", +"Basap": "bdb", +"Emberá-Baudó": "bdc", +"Bunama": "bdd", +"Bade": "bde", +"Biage": "bdf", +"Bonggi": "bdg", +"Baka(Sudan)": "bdh", +"Burun": "bdi", +"Bai": "bdj", +"Budukh": "bdk", +"Indonesian Bajau": "bdl", +"Buduma": "bdm", +"Baldemu": "bdn", +"Morom": "bdo", +"Bende": "bdp", +"Bahnar": "bdq", +"WestCoast Bajau": "bdr", +"Burunge": "bds", +"Bokoto": "bdt", +"Oroko": "bdu", +"BodoParja": "bdv", +"Baham": "bdw", +"Budong-Budong": "bdx", +"Bandjalang": "bdy", +"Badeshi": "bdz", +"Beaver": "bea", +"Bebele": "beb", +"Iceve-Maci": "bec", +"Bedoanas": "bed", +"Byangsi": "bee", +"Benabena": "bef", +"Belait": "beg", +"Biali": "beh", +"Bekati'": "bei", +"Beja": "bej", +"Bebeli": "bek", +"Belarusian": "bel", +"Bemba(Zambia)": "bem", +"Bengali": "ben", +"Beami": "beo", +"Besoa": "bep", +"Beembe": "beq", +"Besme": "bes", +"GuiberouaBéte": "bet", +"Blagar": "beu", +"DaloaBété": "bev", +"Betawi": "bew", +"JurModo": "bex", +"Beli(Papua New Guinea)": "bey", +"Bena(Tanzania)": "bez", +"Bari": "bfa", +"PauriBareli": "bfb", +"NorthernBai": "bfc", +"Bafut": "bfd", +"Betaf": "bfe", +"Bofi": "bff", +"Busang Kayan": "bfg", +"Blafe": "bfh", +"British Sign Language": "bfi", +"Bafanji": "bfj", +"BanKhor Sign Language": "bfk", +"Banda-Ndélé": "bfl", +"Mmen": "bfm", +"Bunak": "bfn", +"MalbaBirifor": "bfo", +"Beba": "bfp", +"Badaga": "bfq", +"Bazigar": "bfr", +"SouthernBai": "bfs", +"Balti": "bft", +"Gahri": "bfu", +"Bondo": "bfw", +"Bantayanon": "bfx", +"Bagheli": "bfy", +"Mahasu Pahari": "bfz", +"Gwamhi-Wuri": "bga", +"Bobongko": "bgb", +"Haryanvi": "bgc", +"RathwiBareli": "bgd", +"Bauria": "bge", +"Bangandu": "bgf", +"Bugun": "bgg", +"Giangan": "bgi", +"Bangolan": "bgj", +"Bit": "bgk", +"Bo(Laos)": "bgl", +"Western Balochi": "bgn", +"Baga Koga": "bgo", +"Eastern Balochi": "bgp", +"Bagri": "bgq", +"Bawm Chin": "bgr", +"Tagabawa": "bgs", +"Bughotu": "bgt", +"Mbongno": "bgu", +"Warkay-Bipim": "bgv", +"Bhatri": "bgw", +"BalkanGagauz Turkish": "bgx", +"Benggoi": "bgy", +"Banggai": "bgz", +"Bharia": "bha", +"Bhili": "bhb", +"Biga": "bhc", +"Bhadrawahi": "bhd", +"Bhaya": "bhe", +"Odiai": "bhf", +"Binandere": "bhg", +"Bukharic": "bhh", +"Bhilali": "bhi", +"Bahing": "bhj", +"Bimin": "bhl", +"Bathari": "bhm", +"Bohtan Neo-Aramaic": "bhn", +"Bhojpuri": "bho", +"Bima": "bhp", +"TukangBesi South": "bhq", +"BaraMalagasy": "bhr", +"Buwal": "bhs", +"Bhattiyali": "bht", +"Bhunjia": "bhu", +"Bahau": "bhv", +"Biak": "bhw", +"Bhalay": "bhx", +"Bhele": "bhy", +"Bada(Indonesia)": "bhz", +"Badimaya": "bia", +"Bissa": "bib", +"Bidiyo": "bid", +"Bepour": "bie", +"Biafada": "bif", +"Biangai": "big", +"Bikol": "bik", +"Bile": "bil", +"Bimoba": "bim", +"Bini": "bin", +"Nai": "bio", +"Bila": "bip", +"Bipi": "biq", +"Bisorio": "bir", +"Bislama": "bis", +"Berinomo": "bit", +"Biete": "biu", +"Southern Birifor": "biv", +"Kol (Cameroon)": "biw", +"Bijori": "bix", +"Birhor": "biy", +"Baloi": "biz", +"Budza": "bja", +"Banggarla": "bjb", +"Bariji": "bjc", +"Biao-JiaoMien": "bje", +"Barzani Jewish Neo-Aramaic": "bjf", +"Bidyogo": "bjg", +"Bahinemo": "bjh", +"Burji": "bji", +"Kanauji": "bjj", +"Barok": "bjk", +"Bulu(Papua New Guinea)": "bjl", +"Bajelani": "bjm", +"Banjar": "bjn", +"Mid-Southern Banda": "bjo", +"Fanamaket": "bjp", +"Binumarien": "bjr", +"Bajan": "bjs", +"Balanta-Ganja": "bjt", +"Busuu": "bju", +"Bedjond": "bjv", +"Bakwé": "bjw", +"Banao Itneg": "bjx", +"Bayali": "bjy", +"Baruga": "bjz", +"Kyak": "bka", +"Baka (Cameroon)": "bkc", +"Binukid": "bkd", +"Beeke": "bkf", +"Buraka": "bkg", +"Bakoko": "bkh", +"Baki": "bki", +"Pande": "bkj", +"Brokskat": "bkk", +"Berik": "bkl", +"Kom(Cameroon)": "bkm", +"Bukitan": "bkn", +"Kwa'": "bko", +"Boko(Democratic Republic of Congo)": "bkp", +"Bakairí": "bkq", +"Bakumpai": "bkr", +"NorthernSorsoganon": "bks", +"Boloki": "bkt", +"Buhid": "bku", +"Bekwarra": "bkv", +"Bekwil": "bkw", +"Baikeno": "bkx", +"Bokyi": "bky", +"Bungku": "bkz", +"Siksika": "bla", +"Bilua": "blb", +"BellaCoola": "blc", +"Bolango": "bld", +"Balanta-Kentohe": "ble", +"Buol": "blf", +"Kuwaa": "blh", +"Bolia": "bli", +"Bolongan": "blj", +"Pa'o Karen": "blk", +"Biloxi": "bll", +"Beli(Sudan)": "blm", +"Southern Catanduanes Bicolano": "bln", +"Anii": "blo", +"Blablanga": "blp", +"Baluan-Pam": "blq", +"Blang": "blr", +"Balaesang": "bls", +"Tai Dam": "blt", +"Bolo": "blv", +"Balangao": "blw", +"Mag-Indi Ayta": "blx", +"Notre": "bly", +"Balantak": "blz", +"Lame": "bma", +"Bembe": "bmb", +"Biem": "bmc", +"BagaManduri": "bmd", +"Limassa": "bme", +"Bom": "bmf", +"Bamwe": "bmg", +"Kein": "bmh", +"Bagirmi": "bmi", +"Bote-Majhi": "bmj", +"Ghayavi": "bmk", +"Bomboli": "bml", +"Northern Betsimisaraka Malagasy": "bmm", +"Bina(Papua New Guinea)": "bmn", +"Bambalang": "bmo", +"Bulgebi": "bmp", +"Bomu": "bmq", +"Muinane": "bmr", +"BilmaKanuri": "bms", +"BiaoMon": "bmt", +"Burum-Mindik": "bmu", +"Bum": "bmv", +"Bomwali": "bmw", +"Baimak": "bmx", +"Baramu": "bmz", +"Bonerate": "bna", +"Bookan": "bnb", +"Bontok": "bnc", +"Banda(Indonesia)": "bnd", +"Bintauna": "bne", +"Masiwang": "bnf", +"Benga": "bng", +"Bangi": "bni", +"EasternTawbuid": "bnj", +"Bierebo": "bnk", +"Boon": "bnl", +"Batanga": "bnm", +"Bunun": "bnn", +"Bantoanon": "bno", +"Bola": "bnp", +"Bantik": "bnq", +"Butmas-Tur": "bnr", +"Bundeli": "bns", +"Bentong": "bnu", +"Bonerif": "bnv", +"Bisis": "bnw", +"Bangubangu": "bnx", +"Bintulu": "bny", +"Beezen": "bnz", +"Bora": "boa", +"Aweer": "bob", +"Tibetan": "bod", +"Mundabli": "boe", +"Bolon": "bof", +"Bamako Sign Language": "bog", +"Boma": "boh", +"Barbareño": "boi", +"Anjam": "boj", +"Bonjo": "bok", +"Bole": "bol", +"Berom": "bom", +"Bine": "bon", +"Tiemacèwè Bozo": "boo", +"Bonkiman": "bop", +"Bogaya": "boq", +"Borôro": "bor", +"Bosnian": "bos", +"Bongo": "bot", +"Bondei": "bou", +"Tuwuli": "bov", +"Rema": "bow", +"Buamu": "box", +"Bodo(Central African Republic)": "boy", +"Tiéyaxo Bozo": "boz", +"Dakaka": "bpa", +"Banda-Banda": "bpd", +"Bauni": "bpe", +"Bonggo": "bpg", +"Botlikh": "bph", +"Bagupi": "bpi", +"Binji": "bpj", +"Orowe": "bpk", +"Broome Pearling Lugger Pidgin": "bpl", +"Biyom": "bpm", +"DzaoMin": "bpn", +"Anasi": "bpo", +"Kaure": "bpp", +"Banda Malay": "bpq", +"KoronadalBlaan": "bpr", +"SaranganiBlaan": "bps", +"Barrow Point": "bpt", +"Bongu": "bpu", +"BianMarind": "bpv", +"Bo (Papua New Guinea)": "bpw", +"PalyaBareli": "bpx", +"Bishnupriya": "bpy", +"Bilba": "bpz", +"Tchumbuli": "bqa", +"Bagusa": "bqb", +"Boko (Benin)": "bqc", +"Bung": "bqd", +"BagaKaloum": "bqf", +"Bago-Kusuntu": "bqg", +"Baima": "bqh", +"Bakhtiari": "bqi", +"Bandial": "bqj", +"Banda-Mbrès": "bqk", +"Bilakura": "bql", +"Wumboko": "bqm", +"Bulgarian Sign Language": "bqn", +"Balo": "bqo", +"Busa": "bqp", +"Biritai": "bqq", +"Burusu": "bqr", +"Bosngun": "bqs", +"Bamukumbit": "bqt", +"Boguru": "bqu", +"Begbere-Ejar": "bqv", +"Buru (Nigeria)": "bqw", +"Baangi": "bqx", +"BengkalaSign Language": "bqy", +"Bakaka": "bqz", +"Braj": "bra", +"Lave": "brb", +"Berbice Creole Dutch": "brc", +"Baraamu": "brd", +"Breton": "bre", +"Bera": "brf", +"Baure": "brg", +"Brahui": "brh", +"Mokpwe": "bri", +"Bieria": "brj", +"Birked": "brk", +"Birwa": "brl", +"Barambu": "brm", +"Boruca": "brn", +"Brokkat": "bro", +"Barapasi": "brp", +"Breri": "brq", +"Birao": "brr", +"Baras": "brs", +"Bitare": "brt", +"EasternBru": "bru", +"Western Bru": "brv", +"Bellari": "brw", +"Bodo (India)": "brx", +"Burui": "bry", +"Bilbil": "brz", +"Abinomn": "bsa", +"Brunei Bisaya": "bsb", +"Bassari": "bsc", +"Wushi": "bse", +"Bauchi": "bsf", +"Bashkardi": "bsg", +"Kati": "bsh", +"Bassossi": "bsi", +"Bangwinji": "bsj", +"Burushaski": "bsk", +"Basa-Gumna": "bsl", +"Busami": "bsm", +"Barasana-Eduria": "bsn", +"Buso": "bso", +"Baga Sitemu": "bsp", +"Bassa": "bsq", +"Bassa-Kontagora": "bsr", +"Akoose": "bss", +"Basketo": "bst", +"Bahonsuai": "bsu", +"BagaSobané": "bsv", +"Baiso": "bsw", +"Yangkam": "bsx", +"Sabah Bisaya": "bsy", +"Bata": "bta", +"Bati(Cameroon)": "btc", +"BatakDairi": "btd", +"Gamo-Ningi": "bte", +"Birgit": "btf", +"GagnoaBété": "btg", +"Biatah Bidayuh": "bth", +"Burate": "bti", +"Bacanese Malay": "btj", +"BatakMandailing": "btm", +"Ratagnon": "btn", +"Rinconada Bikol": "bto", +"Budibud": "btp", +"Batek": "btq", +"Baetora": "btr", +"BatakSimalungun": "bts", +"Bete-Bendi": "btt", +"Batu": "btu", +"Bateri": "btv", +"Butuanon": "btw", +"Batak Karo": "btx", +"Bobot": "bty", +"Batak Alas-Kluet": "btz", +"Buriat": "bua", +"Bua": "bub", +"Bushi": "buc", +"Ntcham": "bud", +"Beothuk": "bue", +"Bushoong": "buf", +"Buginese": "bug", +"Younuo Bunu": "buh", +"Bongili": "bui", +"Basa-Gurmana": "buj", +"Bugawac": "buk", +"Bulgarian": "bul", +"Bulu (Cameroon)": "bum", +"Sherbro": "bun", +"Terei": "buo", +"Busoa": "bup", +"Brem": "buq", +"Bokobaru": "bus", +"Bungain": "but", +"Budu": "buu", +"Bun": "buv", +"Bubi": "buw", +"Boghom": "bux", +"BullomSo": "buy", +"Bukwen": "buz", +"Barein": "bva", +"Bube": "bvb", +"Baelelea": "bvc", +"Baeggu": "bvd", +"BerauMalay": "bve", +"Boor": "bvf", +"Bonkeng": "bvg", +"Bure": "bvh", +"BelandaViri": "bvi", +"Baan": "bvj", +"Bukat": "bvk", +"BolivianSign Language": "bvl", +"Bamunka": "bvm", +"Buna": "bvn", +"Bolgo": "bvo", +"Bumang": "bvp", +"Birri": "bvq", +"Burarra": "bvr", +"Bati(Indonesia)": "bvt", +"BukitMalay": "bvu", +"Baniva": "bvv", +"Boga": "bvw", +"Dibole": "bvx", +"Baybayanon": "bvy", +"Bauzi": "bvz", +"Bwatoo": "bwa", +"Namosi-Naitasiri-Serua": "bwb", +"Bwile": "bwc", +"Bwaidoka": "bwd", +"BweKaren": "bwe", +"Boselewa": "bwf", +"Barwe": "bwg", +"Bishuo": "bwh", +"Baniwa": "bwi", +"Láá Láá Bwamu": "bwj", +"Bauwaki": "bwk", +"Bwela": "bwl", +"Biwat": "bwm", +"WunaiBunu": "bwn", +"Boro(Ethiopia)": "bwo", +"MandoboBawah": "bwp", +"SouthernBobo Madaré": "bwq", +"Bura-Pabir": "bwr", +"Bomboma": "bws", +"Bafaw-Balong": "bwt", +"Buli(Ghana)": "bwu", +"Bwa": "bww", +"Bu-NaoBunu": "bwx", +"Cwi Bwamu": "bwy", +"Bwisi": "bwz", +"Bauro": "bxa", +"BelandaBor": "bxb", +"Molengue": "bxc", +"Pela": "bxd", +"Birale": "bxe", +"Bilur": "bxf", +"Bangala": "bxg", +"Buhutu": "bxh", +"Pirlatapa": "bxi", +"Bayungu": "bxj", +"Bukusu": "bxk", +"Jalkunan": "bxl", +"MongoliaBuriat": "bxm", +"Burduna": "bxn", +"Barikanchi": "bxo", +"Bebil": "bxp", +"Beele": "bxq", +"Russia Buriat": "bxr", +"Busam": "bxs", +"China Buriat": "bxu", +"Berakou": "bxv", +"Bankagooma": "bxw", +"Binahari": "bxz", +"Batak": "bya", +"Bikya": "byb", +"Ubaghara": "byc", +"Benyadu'": "byd", +"Pouye": "bye", +"Bete": "byf", +"Baygo": "byg", +"Bhujel": "byh", +"Buyu": "byi", +"Bina (Nigeria)": "byj", +"Biao": "byk", +"Bayono": "byl", +"Bidyara": "bym", +"Bilin": "byn", +"Biyo": "byo", +"Bumaji": "byp", +"Basay": "byq", +"Baruya": "byr", +"Burak": "bys", +"Berti": "byt", +"Medumba": "byv", +"Belhariya": "byw", +"Qaqet": "byx", +"Banaro": "byz", +"Bandi": "bza", +"Andio": "bzb", +"Southern Betsimisaraka Malagasy": "bzc", +"Bribri": "bzd", +"JenaamaBozo": "bze", +"Boikin": "bzf", +"Babuza": "bzg", +"MaposBuang": "bzh", +"Bisu": "bzi", +"Belize Kriol English": "bzj", +"Nicaragua Creole English": "bzk", +"Boano(Sulawesi)": "bzl", +"Bolondo": "bzm", +"Boano (Maluku)": "bzn", +"Bozaba": "bzo", +"Kemberano": "bzp", +"Buli (Indonesia)": "bzq", +"Biri": "bzr", +"Brazilian Sign Language": "bzs", +"Brithenig": "bzt", +"Burmeso": "bzu", +"Bebe": "bzv", +"Basa(Nigeria)": "bzw", +"HainyaxoBozo": "bzx", +"Obanliku": "bzy", +"Evant": "bzz", +"Chortí": "caa", +"Garifuna": "cab", +"Chuj": "cac", +"Caddo": "cad", +"Lehar": "cae", +"SouthernCarrier": "caf", +"Nivaclé": "cag", +"Cahuarano": "cah", +"Chané": "caj", +"Kaqchikel": "cak", +"Carolinian": "cal", +"Cemuhî": "cam", +"Chambri": "can", +"Chácobo": "cao", +"Chipaya": "cap", +"Car Nicobarese": "caq", +"Galibi Carib": "car", +"Tsimané": "cas", +"Catalan": "cat", +"Cavineña": "cav", +"Callawalla": "caw", +"Chiquitano": "cax", +"Cayuga": "cay", +"Canichana": "caz", +"Cabiyarí": "cbb", +"Carapana": "cbc", +"Carijona": "cbd", +"Chimila": "cbg", +"Chachi": "cbi", +"EdeCabe": "cbj", +"Chavacano": "cbk", +"Bualkhaw Chin": "cbl", +"Nyahkur": "cbn", +"Izora": "cbo", +"Tsucuba": "cbq", +"Cashibo-Cacataibo": "cbr", +"Cashinahua": "cbs", +"Chayahuita": "cbt", +"Candoshi-Shapra": "cbu", +"Cacua": "cbv", +"Kinabalian": "cbw", +"Carabayo": "cby", +"Chamicuro": "ccc", +"Cafundo Creole": "ccd", +"Chopi": "cce", +"SambaDaka": "ccg", +"Atsam": "cch", +"Kasanga": "ccj", +"Cutchi-Swahili": "ccl", +"Malaccan Creole Malay": "ccm", +"ComaltepecChinantec": "cco", +"Chakma": "ccp", +"Cacaopera": "ccr", +"Choni": "cda", +"Chenchu": "cde", +"Chiru": "cdf", +"Chambeali": "cdh", +"Chodri": "cdi", +"Churahi": "cdj", +"Chepang": "cdm", +"Chaudangsi": "cdn", +"Min Dong Chinese": "cdo", +"Cinda-Regi-Tiyal": "cdr", +"ChadianSign Language": "cds", +"Chadong": "cdy", +"Koda": "cdz", +"Lower Chehalis": "cea", +"Cebuano": "ceb", +"Chamacoco": "ceg", +"Eastern Khumi Chin": "cek", +"Cen": "cen", +"Czech": "ces", +"Centúúm": "cet", +"Ekai Chin": "cey", +"Dijim-Bwilim": "cfa", +"Cara": "cfd", +"ComoKarim": "cfg", +"FalamChin": "cfm", +"Changriwa": "cga", +"Kagayanen": "cgc", +"Chiga": "cgg", +"Chocangacakha": "cgk", +"Chamorro": "cha", +"Chibcha": "chb", +"Catawba": "chc", +"HighlandOaxaca Chontal": "chd", +"Chechen": "che", +"TabascoChontal": "chf", +"Chagatai": "chg", +"Chinook": "chh", +"OjitlánChinantec": "chj", +"Chuukese": "chk", +"Cahuilla": "chl", +"Mari (Russia)": "chm", +"Chinookjargon": "chn", +"Choctaw": "cho", +"Chipewyan": "chp", +"Quiotepec Chinantec": "chq", +"Cherokee": "chr", +"Cholón": "cht", +"ChurchSlavic": "chu", +"Chuvash": "chv", +"Chuwabu": "chw", +"Chantyal": "chx", +"Cheyenne": "chy", +"Ozumacín Chinantec": "chz", +"Cia-Cia": "cia", +"CiGbe": "cib", +"Chickasaw": "cic", +"Chimariko": "cid", +"Cineni": "cie", +"Chinali": "cih", +"ChitkuliKinnauri": "cik", +"Cimbrian": "cim", +"CintaLarga": "cin", +"Chiapanec": "cip", +"Tiri": "cir", +"Chippewa": "ciw", +"Chaima": "ciy", +"WesternCham": "cja", +"Chru": "cje", +"UpperChehalis": "cjh", +"Chamalal": "cji", +"Chokwe": "cjk", +"EasternCham": "cjm", +"Chenapian": "cjn", +"AshéninkaPajonal": "cjo", +"Cabécar": "cjp", +"Shor": "cjs", +"Chuave": "cjv", +"Jinyu Chinese": "cjy", +"CentralKurdish": "ckb", +"Chak": "ckh", +"Cibak": "ckl", +"Chakavian": "ckm", +"Kaang Chin": "ckn", +"Anufo": "cko", +"Kajakse": "ckq", +"Kairak": "ckr", +"Tayo": "cks", +"Chukot": "ckt", +"Koasati": "cku", +"Kavalan": "ckv", +"Caka": "ckx", +"Cakfem-Mushere": "cky", +"Cakchiquel-Quiché Mixed Language": "ckz", +"Ron": "cla", +"Chilcotin": "clc", +"Chaldean Neo-Aramaic": "cld", +"LealaoChinantec": "cle", +"Chilisso": "clh", +"Chakali": "cli", +"Laitu Chin": "clj", +"Idu-Mishmi": "clk", +"Chala": "cll", +"Clallam": "clm", +"Lowland Oaxaca Chontal": "clo", +"Lautu Chin": "clt", +"Caluyanun": "clu", +"Chulym": "clw", +"Eastern Highland Chatino": "cly", +"Maa": "cma", +"Cerma": "cme", +"ClassicalMongolian": "cmg", +"Emberá-Chamí": "cmi", +"Campalagian": "cml", +"Michigamea": "cmm", +"MandarinChinese": "cmn", +"CentralMnong": "cmo", +"Mro Chin": "cmr", +"Messapic": "cms", +"Camtho": "cmt", +"Changthang": "cna", +"ChinbonChin": "cnb", +"Côông": "cnc", +"NorthernQiang": "cng", +"HakaChin": "cnh", +"Asháninka": "cni", +"KhumiChin": "cnk", +"Lalana Chinantec": "cnl", +"Con": "cno", +"Northern Ping Chinese": "cnp", +"Montenegrin": "cnr", +"CentralAsmat": "cns", +"Tepetotutla Chinantec": "cnt", +"Chenoua": "cnu", +"NgawnChin": "cnw", +"MiddleCornish": "cnx", +"Cocos Islands Malay": "coa", +"Chicomuceltec": "cob", +"Cocopa": "coc", +"Cocama-Cocamilla": "cod", +"Koreguaje": "coe", +"Colorado": "cof", +"Chong": "cog", +"Chonyi-Dzihana-Kauma": "coh", +"Cochimi": "coj", +"SantaTeresa Cora": "cok", +"Columbia-Wenatchi": "col", +"Comanche": "com", +"Cofán": "con", +"Comox": "coo", +"Coptic": "cop", +"Coquille": "coq", +"Cornish": "cor", +"Corsican": "cos", +"Caquinte": "cot", +"Wamey": "cou", +"CaoMiao": "cov", +"Cowlitz": "cow", +"Nanti": "cox", +"Chochotec": "coz", +"Palantla Chinantec": "cpa", +"Ucayali-YurúaAshéninka": "cpb", +"AjyíninkaApurucayali": "cpc", +"CappadocianGreek": "cpg", +"ChinesePidgin English": "cpi", +"Cherepon": "cpn", +"Kpeego": "cpo", +"Capiznon": "cps", +"PichisAshéninka": "cpu", +"Pu-XianChinese": "cpx", +"South Ucayali Ashéninka": "cpy", +"Chuanqiandian Cluster Miao": "cqd", +"Chara": "cra", +"IslandCarib": "crb", +"Lonwolwol": "crc", +"Coeurd'Alene": "crd", +"Cree": "cre", +"Caramanta": "crf", +"Michif": "crg", +"CrimeanTatar": "crh", +"Sãotomense": "cri", +"SouthernEast Cree": "crj", +"PlainsCree": "crk", +"NorthernEast Cree": "crl", +"MooseCree": "crm", +"ElNayar Cora": "crn", +"Crow": "cro", +"Iyo'wujwaChorote": "crq", +"Carolina Algonquian": "crr", +"Seselwa Creole French": "crs", +"Iyojwa'ja Chorote": "crt", +"Chaura": "crv", +"Chrau": "crw", +"Carrier": "crx", +"Cori": "cry", +"Cruzeño": "crz", +"Chiltepec Chinantec": "csa", +"Kashubian": "csb", +"CatalanSign Language": "csc", +"ChiangmaiSign Language": "csd", +"CzechSign Language": "cse", +"Cuba Sign Language": "csf", +"Chilean Sign Language": "csg", +"AshoChin": "csh", +"CoastMiwok": "csi", +"Songlai Chin": "csj", +"Jola-Kasa": "csk", +"Chinese Sign Language": "csl", +"CentralSierra Miwok": "csm", +"ColombianSign Language": "csn", +"SochiapamChinantec": "cso", +"Southern Ping Chinese": "csp", +"CroatiaSign Language": "csq", +"CostaRican Sign Language": "csr", +"SouthernOhlone": "css", +"Northern Ohlone": "cst", +"Sumtu Chin": "csv", +"Swampy Cree": "csw", +"Cambodian Sign Language": "csx", +"Siyin Chin": "csy", +"Coos": "csz", +"TataltepecChatino": "cta", +"Chetco": "ctc", +"Tedim Chin": "ctd", +"Tepinapa Chinantec": "cte", +"Chittagonian": "ctg", +"Thaiphum Chin": "cth", +"TlacoatzintepecChinantec": "ctl", +"Chitimacha": "ctm", +"Chhintange": "ctn", +"Emberá-Catío": "cto", +"Western Highland Chatino": "ctp", +"Northern Catanduanes Bicolano": "cts", +"WayanadChetti": "ctt", +"Chol": "ctu", +"Moundadan Chetty": "cty", +"ZacatepecChatino": "ctz", +"Cua": "cua", +"Cubeo": "cub", +"UsilaChinantec": "cuc", +"Cung": "cug", +"Chuka": "cuh", +"Cuiba": "cui", +"MashcoPiro": "cuj", +"SanBlas Kuna": "cuk", +"Culina": "cul", +"Cumanagoto": "cuo", +"Cupeño": "cup", +"Cun": "cuq", +"Chhulung": "cur", +"TeutilaCuicatec": "cut", +"TaiYa": "cuu", +"Cuvok": "cuv", +"Chukwa": "cuw", +"TepeuxilaCuicatec": "cux", +"Cuitlatec": "cuy", +"Chug": "cvg", +"Valle Nacional Chinantec": "cvn", +"Kabwa": "cwa", +"Maindo": "cwb", +"Woods Cree": "cwd", +"Kwere": "cwe", +"Chewong": "cwg", +"Kuwaataay": "cwt", +"Nopala Chatino": "cya", +"Cayubaba": "cyb", +"Welsh": "cym", +"Cuyonon": "cyo", +"Huizhou Chinese": "czh", +"Knaanic": "czk", +"ZenzontepecChatino": "czn", +"Min Zhong Chinese": "czo", +"ZotungChin": "czt", +"Dangaléat": "daa", +"Dambi": "dac", +"Marik": "dad", +"Duupa": "dae", +"Dagbani": "dag", +"Gwahatike": "dah", +"Day": "dai", +"DarFur Daju": "daj", +"Dakota": "dak", +"Dahalo": "dal", +"Damakawa": "dam", +"Danish": "dan", +"DaaiChin": "dao", +"Dandami Maria": "daq", +"Dargwa": "dar", +"Daho-Doo": "das", +"DarSila Daju": "dau", +"Taita": "dav", +"Davawenyo": "daw", +"Dayi": "dax", +"Dao": "daz", +"BangiMe": "dba", +"Deno": "dbb", +"Dadiya": "dbd", +"Dabe": "dbe", +"Edopi": "dbf", +"DogulDom Dogon": "dbg", +"Doka": "dbi", +"Ida'an": "dbj", +"Dyirbal": "dbl", +"Duguri": "dbm", +"Duriankere": "dbn", +"Dulbu": "dbo", +"Duwai": "dbp", +"Daba": "dbq", +"Dabarre": "dbr", +"Ben Tey Dogon": "dbt", +"BondumDom Dogon": "dbu", +"Dungu": "dbv", +"Bankan Tey Dogon": "dbw", +"Dibiyaso": "dby", +"Deccan": "dcc", +"Negerhollands": "dcr", +"Dadi Dadi": "dda", +"Dongotono": "ddd", +"Doondo": "dde", +"Fataluku": "ddg", +"West Goodenough": "ddi", +"Jaru": "ddj", +"Dendi (Benin)": "ddn", +"Dido": "ddo", +"Dhudhuroa": "ddr", +"Donno So Dogon": "dds", +"Dawera-Daweloor": "ddw", +"Dagik": "dec", +"Dedua": "ded", +"Dewoin": "dee", +"Dezfuli": "def", +"Degema": "deg", +"Dehwari": "deh", +"Demisa": "dei", +"Dek": "dek", +"Delaware": "del", +"Dem": "dem", +"Slave (Athapascan)": "den", +"PidginDelaware": "dep", +"Dendi(Central African Republic)": "deq", +"Deori": "der", +"Desano": "des", +"German": "deu", +"Domung": "dev", +"Dengese": "dez", +"SouthernDagaare": "dga", +"Bunoge Dogon": "dgb", +"CasiguranDumagat Agta": "dgc", +"Dagaari Dioula": "dgd", +"Degenan": "dge", +"Doga": "dgg", +"Dghwede": "dgh", +"NorthernDagara": "dgi", +"Dagba": "dgk", +"Andaandi": "dgl", +"Dagoman": "dgn", +"Dogri(individual language)": "dgo", +"Dogrib": "dgr", +"Dogoso": "dgs", +"Ndra'ngith": "dgt", +"Daungwurrung": "dgw", +"Doghoro": "dgx", +"Daga": "dgz", +"Dhundari": "dhd", +"Dhangu": "dhg", +"Dhimal": "dhi", +"Dhalandji": "dhl", +"Zemba": "dhm", +"Dhanki": "dhn", +"Dhodia": "dho", +"Dhargari": "dhr", +"Dhaiso": "dhs", +"Dhurga": "dhu", +"Dehu": "dhv", +"Dhanwar(Nepal)": "dhw", +"Dhungaloo": "dhx", +"Dia": "dia", +"South Central Dinka": "dib", +"Lakota Dida": "dic", +"Didinga": "did", +"Dieri": "dif", +"Digo": "dig", +"Kumiai": "dih", +"Dimbong": "dii", +"Dai": "dij", +"Southwestern Dinka": "dik", +"Dilling": "dil", +"Dime": "dim", +"Dinka": "din", +"Dibo": "dio", +"NortheasternDinka": "dip", +"Dimli (individual language)": "diq", +"Dirim": "dir", +"Dimasa": "dis", +"Diriku": "diu", +"Dhivehi": "div", +"NorthwesternDinka": "diw", +"DixonReef": "dix", +"Diuwe": "diy", +"Ding": "diz", +"Djadjawurrung": "dja", +"Djinba": "djb", +"Dar Daju Daju": "djc", +"Djamindjung": "djd", +"Zarma": "dje", +"Djangun": "djf", +"Djinang": "dji", +"Djeebbana": "djj", +"Eastern Maroon Creole": "djk", +"Jamsay Dogon": "djm", +"Djauan": "djn", +"Jangkang": "djo", +"Djambarrpuyngu": "djr", +"Kapriman": "dju", +"Djawi": "djw", +"Dakpakha": "dka", +"Kadung": "dkg", +"Dakka": "dkk", +"Kuijau": "dkr", +"SoutheasternDinka": "dks", +"Mazagway": "dkx", +"Dolgan": "dlg", +"Dahalik": "dlk", +"Dalmatian": "dlm", +"Darlong": "dln", +"Duma": "dma", +"Mombo Dogon": "dmb", +"Dimir": "dmc", +"Madhi Madhi": "dmd", +"Dugwor": "dme", +"Medefaidrin": "dmf", +"UpperKinabatangan": "dmg", +"Domaaki": "dmk", +"Dameli": "dml", +"Dama": "dmm", +"Kemezung": "dmo", +"EastDamar": "dmr", +"Dampelas": "dms", +"Dubu": "dmu", +"Dumpas": "dmv", +"Mudburra": "dmw", +"Dema": "dmx", +"Demta": "dmy", +"UpperGrand Valley Dani": "dna", +"Daonda": "dnd", +"Ndendeule": "dne", +"Dungan": "dng", +"LowerGrand Valley Dani": "dni", +"Dan": "dnj", +"Dengka": "dnk", +"Dzùùngoo": "dnn", +"Ndrulo": "dno", +"Danaru": "dnr", +"MidGrand Valley Dani": "dnt", +"Danau": "dnu", +"Danu": "dnv", +"Western Dani": "dnw", +"Dení": "dny", +"Dom": "doa", +"Dobu": "dob", +"Northern Dong": "doc", +"Doe": "doe", +"Domu": "dof", +"Dong": "doh", +"Dogri (macrolanguage)": "doi", +"Dondo": "dok", +"Doso": "dol", +"Toura(Papua New Guinea)": "don", +"Dongo": "doo", +"Lukpa": "dop", +"Dominican Sign Language": "doq", +"Dori'o": "dor", +"Dogosé": "dos", +"Dass": "dot", +"Dombe": "dov", +"Doyayo": "dow", +"Bussa": "dox", +"Dompo": "doy", +"Dorze": "doz", +"Papar": "dpp", +"Dair": "drb", +"Minderico": "drc", +"Darmiya": "drd", +"Dolpo": "dre", +"Rungus": "drg", +"C'lela": "dri", +"Darling": "drl", +"WestDamar": "drn", +"Daro-MatuMelanau": "dro", +"Dura": "drq", +"Gedeo": "drs", +"Drents": "drt", +"Rukai": "dru", +"Darai": "dry", +"LowerSorbian": "dsb", +"DutchSign Language": "dse", +"Daasanach": "dsh", +"Disa": "dsi", +"Danish Sign Language": "dsl", +"Dusner": "dsn", +"Desiya": "dso", +"Tadaksahak": "dsq", +"Daur": "dta", +"Labuk-Kinabatangan Kadazan": "dtb", +"Ditidaht": "dtd", +"Adithinngithigh": "dth", +"AnaTinga Dogon": "dti", +"Tene Kan Dogon": "dtk", +"TomoKan Dogon": "dtm", +"Daatsʼíin": "dtn", +"Tommo So Dogon": "dto", +"CentralDusun": "dtp", +"Lotud": "dtr", +"Toro So Dogon": "dts", +"ToroTegu Dogon": "dtt", +"Tebul Ure Dogon": "dtu", +"Dotyali": "dty", +"Duala": "dua", +"Dubli": "dub", +"Duna": "duc", +"UmirayDumaget Agta": "due", +"Dumbea": "duf", +"Duruma": "dug", +"DungraBhil": "duh", +"Dumun": "dui", +"Duduela": "duk", +"AlabatIsland Agta": "dul", +"Middle Dutch (ca. 1050-1350)": "dum", +"DusunDeyah": "dun", +"Dupaninan Agta": "duo", +"Duano": "dup", +"DusunMalang": "duq", +"Dii": "dur", +"Dumi": "dus", +"Drung": "duu", +"Duvle": "duv", +"Dusun Witu": "duw", +"Duungooma": "dux", +"DicamayAgta": "duy", +"Duli": "duz", +"Duau": "dva", +"Diri": "dwa", +"Dawik Kui": "dwk", +"Dawro": "dwr", +"Dutton World Speedwords": "dws", +"Dhuwal": "dwu", +"Dawawa": "dww", +"Dhuwaya": "dwy", +"Dewas Rai": "dwz", +"Dyan": "dya", +"Dyaberdyaber": "dyb", +"Dyugun": "dyd", +"VillaViciosa Agta": "dyg", +"DjiminiSenoufo": "dyi", +"YandaDom Dogon": "dym", +"Dyangadi": "dyn", +"Jola-Fonyi": "dyo", +"Dyula": "dyu", +"Dyaabugay": "dyy", +"Tunzu": "dza", +"Djiwarli": "dze", +"Dazaga": "dzg", +"Dzalakha": "dzl", +"Dzando": "dzn", +"Dzongkha": "dzo", +"Karenggapa": "eaa", +"Beginci": "ebc", +"Ebughu": "ebg", +"Eastern Bontok": "ebk", +"Teke-Ebo": "ebo", +"Ebrié": "ebr", +"Embu": "ebu", +"Eteocretan": "ecr", +"EcuadorianSign Language": "ecs", +"Eteocypriot": "ecy", +"E": "eee", +"Efai": "efa", +"Efe": "efe", +"Efik": "efi", +"Ega": "ega", +"Emilian": "egl", +"Eggon": "ego", +"Egyptian(Ancient)": "egy", +"Miyakubo Sign Language": "ehs", +"Ehueun": "ehu", +"Eipomek": "eip", +"Eitiep": "eit", +"Askopan": "eiv", +"Ejamat": "eja", +"Ekajuk": "eka", +"Ekit": "eke", +"Ekari": "ekg", +"Eki": "eki", +"Standard Estonian": "ekk", +"Kol": "ekl", +"Elip": "ekm", +"Koti": "eko", +"Ekpeye": "ekp", +"Yace": "ekr", +"EasternKayah": "eky", +"Elepi": "ele", +"ElHugeirat": "elh", +"Nding": "eli", +"Elkei": "elk", +"Modern Greek (1453-)": "ell", +"Eleme": "elm", +"El Molo": "elo", +"Elu": "elu", +"Elamite": "elx", +"Emai-Iuleha-Ora": "ema", +"Embaloh": "emb", +"Emerillon": "eme", +"EasternMeohang": "emg", +"Mussau-Emira": "emi", +"EasternManinkakan": "emk", +"Mamulique": "emm", +"Eman": "emn", +"Northern Emberá": "emp", +"Eastern Minyag": "emq", +"PacificGulf Yupik": "ems", +"EasternMuria": "emu", +"Emplawas": "emw", +"Erromintxela": "emx", +"EpigraphicMayan": "emy", +"Mbessa": "emz", +"Apali": "ena", +"Markweeta": "enb", +"En": "enc", +"Ende": "end", +"ForestEnets": "enf", +"English": "eng", +"TundraEnets": "enh", +"Enlhet": "enl", +"Middle English (1100-1500)": "enm", +"Engenni": "enn", +"Enggano": "eno", +"Enga": "enq", +"Emumu": "enr", +"Enu": "enu", +"Enwan(Edu State)": "env", +"Enwan (Akwa Ibom State)": "enw", +"Enxet": "enx", +"Beti(Côte d'Ivoire)": "eot", +"Epie": "epi", +"Esperanto": "epo", +"Eravallan": "era", +"Sie": "erg", +"Eruwa": "erh", +"Ogea": "eri", +"SouthEfate": "erk", +"Horpa": "ero", +"Erre": "err", +"Ersu": "ers", +"Eritai": "ert", +"Erokwanas": "erw", +"EseEjja": "ese", +"Aheri Gondi": "esg", +"Eshtehardi": "esh", +"North Alaskan Inupiatun": "esi", +"Northwest Alaska Inupiatun": "esk", +"Egypt Sign Language": "esl", +"Esuma": "esm", +"Salvadoran Sign Language": "esn", +"EstonianSign Language": "eso", +"Esselen": "esq", +"Central Siberian Yupik": "ess", +"Estonian": "est", +"Central Yupik": "esu", +"Eskayan": "esy", +"Etebi": "etb", +"Etchemin": "etc", +"EthiopianSign Language": "eth", +"Eton(Vanuatu)": "etn", +"Eton(Cameroon)": "eto", +"Edolo": "etr", +"Yekhee": "ets", +"Etruscan": "ett", +"Ejagham": "etu", +"Eten": "etx", +"Semimi": "etz", +"Basque": "eus", +"Even": "eve", +"Uvbie": "evh", +"Evenki": "evn", +"Ewe": "ewe", +"Ewondo": "ewo", +"Extremaduran": "ext", +"Eyak": "eya", +"Keiyo": "eyo", +"Ezaa": "eza", +"Uzekwe": "eze", +"Fasu": "faa", +"Fa D'ambu": "fab", +"Wagi": "fad", +"Fagani": "faf", +"Finongan": "fag", +"Baissa Fali": "fah", +"Faiwol": "fai", +"Faita": "faj", +"Fang(Cameroon)": "fak", +"SouthFali": "fal", +"Fam": "fam", +"Fang (Equatorial Guinea)": "fan", +"Faroese": "fao", +"Palor": "fap", +"Fataleka": "far", +"Persian": "fas", +"Fanti": "fat", +"Fayu": "fau", +"Fala": "fax", +"SouthwesternFars": "fay", +"Northwestern Fars": "faz", +"WestAlbay Bikol": "fbl", +"Quebec Sign Language": "fcs", +"Feroge": "fer", +"FoiaFoia": "ffi", +"MaasinaFulfulde": "ffm", +"Fongoro": "fgr", +"Nobiin": "fia", +"Fyer": "fie", +"Faifi": "fif", +"Fijian": "fij", +"Filipino": "fil", +"Finnish": "fin", +"Fipa": "fip", +"Firan": "fir", +"TornedalenFinnish": "fit", +"Fiwaga": "fiw", +"Kirya-Konzəl": "fkk", +"KvenFinnish": "fkv", +"Kalispel-Pendd'Oreille": "fla", +"Foau": "flh", +"Fali": "fli", +"NorthFali": "fll", +"Flinders Island": "fln", +"Fuliiru": "flr", +"Tsotsitaal": "fly", +"Fe'fe'": "fmp", +"Far Western Muria": "fmu", +"Fanbak": "fnb", +"Fanagalo": "fng", +"Fania": "fni", +"Foodo": "fod", +"Foi": "foi", +"Foma": "fom", +"Fon": "fon", +"Fore": "for", +"Siraya": "fos", +"FernandoPo Creole English": "fpe", +"Fas": "fqs", +"French": "fra", +"CajunFrench": "frc", +"Fordata": "frd", +"Frankish": "frk", +"Middle French (ca. 1400-1600)": "frm", +"OldFrench (842-ca. 1400)": "fro", +"Arpitan": "frp", +"Forak": "frq", +"NorthernFrisian": "frr", +"EasternFrisian": "frs", +"Fortsenal": "frt", +"WesternFrisian": "fry", +"FinnishSign Language": "fse", +"FrenchSign Language": "fsl", +"Finland-Swedish Sign Language": "fss", +"AdamawaFulfulde": "fub", +"Pulaar": "fuc", +"EastFutuna": "fud", +"BorguFulfulde": "fue", +"Pular": "fuf", +"Western Niger Fulfulde": "fuh", +"Bagirmi Fulfulde": "fui", +"Ko": "fuj", +"Fulah": "ful", +"Fum": "fum", +"Fulniô": "fun", +"Central-EasternNiger Fulfulde": "fuq", +"Friulian": "fur", +"Futuna-Aniwa": "fut", +"Furu": "fuu", +"NigerianFulfulde": "fuv", +"Fuyug": "fuy", +"Fur": "fvr", +"Fwâi": "fwa", +"Fwe": "fwe", +"Ga": "gaa", +"Gabri": "gab", +"MixedGreat Andamanese": "gac", +"Gaddang": "gad", +"Guarequena": "gae", +"Gende": "gaf", +"Gagauz": "gag", +"Alekano": "gah", +"Borei": "gai", +"Gadsup": "gaj", +"Gamkonora": "gak", +"Galoli": "gal", +"Kandawo": "gam", +"GanChinese": "gan", +"Gants": "gao", +"Gal": "gap", +"Gata'": "gaq", +"Galeya": "gar", +"AdiwasiGarasia": "gas", +"Kenati": "gat", +"MudhiliGadaba": "gau", +"Nobonob": "gaw", +"Borana-Arsi-GujiOromo": "gax", +"Gayo": "gay", +"West Central Oromo": "gaz", +"Gbaya(Central African Republic)": "gba", +"Kaytetye": "gbb", +"Karadjeri": "gbd", +"Niksek": "gbe", +"Gaikundi": "gbf", +"Gbanziri": "gbg", +"Defi Gbe": "gbh", +"Galela": "gbi", +"BodoGadaba": "gbj", +"Gaddi": "gbk", +"Gamit": "gbl", +"Garhwali": "gbm", +"Mo'da": "gbn", +"NorthernGrebo": "gbo", +"Gbaya-Bossangoa": "gbp", +"Gbaya-Bozoum": "gbq", +"Gbagyi": "gbr", +"GbesiGbe": "gbs", +"Gagadu": "gbu", +"Gbanu": "gbv", +"Gabi-Gabi": "gbw", +"EasternXwla Gbe": "gbx", +"Gbari": "gby", +"ZoroastrianDari": "gbz", +"Mali": "gcc", +"Ganggalida": "gcd", +"Galice": "gce", +"Guadeloupean Creole French": "gcf", +"Grenadian Creole English": "gcl", +"Gaina": "gcn", +"GuianeseCreole French": "gcr", +"Colonia Tovar German": "gct", +"Gade Lohar": "gda", +"Pottangi Ollar Gadaba": "gdb", +"GuguBadhun": "gdc", +"Gedaged": "gdd", +"Gude": "gde", +"Guduf-Gava": "gdf", +"Ga'dang": "gdg", +"Gadjerawang": "gdh", +"Gundi": "gdi", +"Gurdjar": "gdj", +"Gadang": "gdk", +"Dirasha": "gdl", +"Laal": "gdm", +"Umanakaina": "gdn", +"Ghodoberi": "gdo", +"Mehri": "gdq", +"Wipi": "gdr", +"Ghandruk Sign Language": "gds", +"Kungardutyi": "gdt", +"Gudu": "gdu", +"Godwari": "gdx", +"Geruma": "gea", +"Kire": "geb", +"GbolooGrebo": "gec", +"Gade": "ged", +"Gerai": "gef", +"Gengle": "geg", +"HutteriteGerman": "geh", +"Gebe": "gei", +"Gen": "gej", +"Yiwom": "gek", +"Kag-Fer-Jiir-Koor-Ror-Us-Zuksun": "gel", +"Geme": "geq", +"Geser-Gorom": "ges", +"Eviya": "gev", +"Gera": "gew", +"Garre": "gex", +"Enya": "gey", +"Geez": "gez", +"Patpatar": "gfk", +"Gafat": "gft", +"Gao": "gga", +"Gbii": "ggb", +"Gugadj": "ggd", +"Guragone": "gge", +"Gurgula": "ggg", +"Kungarakany": "ggk", +"Ganglau": "ggl", +"Gitua": "ggt", +"Gagu": "ggu", +"Gogodala": "ggw", +"Ghadamès": "gha", +"Hiberno-ScottishGaelic": "ghc", +"Southern Ghale": "ghe", +"Northern Ghale": "ghh", +"Geko Karen": "ghk", +"Ghulfan": "ghl", +"Ghanongga": "ghn", +"Ghomara": "gho", +"Ghera": "ghr", +"Guhu-Samane": "ghs", +"KutangGhale": "ght", +"Kitja": "gia", +"Gibanawa": "gib", +"Gail": "gic", +"Gidar": "gid", +"Gaɓogbo": "gie", +"Goaria": "gig", +"Githabul": "gih", +"Girirra": "gii", +"Gilbertese": "gil", +"Gimi (Eastern Highlands)": "gim", +"Hinukh": "gin", +"Gimi(West New Britain)": "gip", +"GreenGelao": "giq", +"RedGelao": "gir", +"North Giziga": "gis", +"Gitxsan": "git", +"Mulao": "giu", +"WhiteGelao": "giw", +"Gilima": "gix", +"Giyug": "giy", +"South Giziga": "giz", +"Kachi Koli": "gjk", +"Gunditjmara": "gjm", +"Gonja": "gjn", +"Gurindji Kriol": "gjr", +"Gujari": "gju", +"Guya": "gka", +"Magɨ (Madang Province)": "gkd", +"Ndai": "gke", +"Gokana": "gkn", +"Kok-Nar": "gko", +"Guinea Kpelle": "gkp", +"ǂUngkue": "gku", +"ScottishGaelic": "gla", +"Belning": "glb", +"Bon Gula": "glc", +"Nanai": "gld", +"Irish": "gle", +"Galician": "glg", +"Northwest Pashayi": "glh", +"GulaIro": "glj", +"Gilaki": "glk", +"Garlali": "gll", +"Galambu": "glo", +"Glaro-Twabo": "glr", +"Gula (Chad)": "glu", +"Manx": "glv", +"Glavda": "glw", +"Gule": "gly", +"Gambera": "gma", +"Gula'alaa": "gmb", +"Mághdì": "gmd", +"Magɨyi": "gmg", +"Middle High German (ca. 1050-1500)": "gmh", +"MiddleLow German": "gml", +"Gbaya-Mbodomo": "gmm", +"Gimnime": "gmn", +"Mirning": "gmr", +"Gumalu": "gmu", +"Gamo": "gmv", +"Magoma": "gmx", +"MycenaeanGreek": "gmy", +"Mgbolizhia": "gmz", +"Kaansa": "gna", +"Gangte": "gnb", +"Guanche": "gnc", +"Zulgo-Gemzek": "gnd", +"Ganang": "gne", +"Ngangam": "gng", +"Lere": "gnh", +"Gooniyandi": "gni", +"Ngen": "gnj", +"//Gana": "gnk", +"Gangulu": "gnl", +"Ginuman": "gnm", +"Gumatj": "gnn", +"NorthernGondi": "gno", +"Gana": "gnq", +"GurengGureng": "gnr", +"Guntai": "gnt", +"Gnau": "gnu", +"WesternBolivian Guaraní": "gnw", +"Ganzi": "gnz", +"Guro": "goa", +"Playero": "gob", +"Gorakor": "goc", +"Godié": "god", +"Gongduk": "goe", +"Gofa": "gof", +"Gogo": "gog", +"Old High German (ca. 750-1050)": "goh", +"Gobasi": "goi", +"Gowlan": "goj", +"Gowli": "gok", +"Gola": "gol", +"GoanKonkani": "gom", +"Gondi": "gon", +"GoneDau": "goo", +"Yeretuar": "gop", +"Gorap": "goq", +"Gorontalo": "gor", +"Gronings": "gos", +"Gothic": "got", +"Gavar": "gou", +"Gorowa": "gow", +"Gobu": "gox", +"Goundo": "goy", +"Gozarkhani": "goz", +"Gupa-Abawa": "gpa", +"Ghanaian Pidgin English": "gpe", +"Taiap": "gpn", +"Ga'anda": "gqa", +"Guiqiong": "gqi", +"Guana(Brazil)": "gqn", +"Gor": "gqr", +"Qau": "gqu", +"Rajput Garasia": "gra", +"Grebo": "grb", +"AncientGreek (to 1453)": "grc", +"Guruntum-Mbaaru": "grd", +"Madi": "grg", +"Gbiri-Niragu": "grh", +"Ghari": "gri", +"SouthernGrebo": "grj", +"KotaMarudu Talantang": "grm", +"Guarani": "grn", +"Groma": "gro", +"Gorovu": "grq", +"Taznatit": "grr", +"Gresi": "grs", +"Garo": "grt", +"Kistane": "gru", +"Central Grebo": "grv", +"Gweda": "grw", +"Guriaso": "grx", +"Barclayville Grebo": "gry", +"Guramalum": "grz", +"GhanaianSign Language": "gse", +"German Sign Language": "gsg", +"Gusilay": "gsl", +"Guatemalan Sign Language": "gsm", +"Gusan": "gsn", +"SouthwestGbaya": "gso", +"Wasembo": "gsp", +"Greek Sign Language": "gss", +"SwissGerman": "gsw", +"Guató": "gta", +"Aghu-Tharnggala": "gtu", +"Shiki": "gua", +"Guajajára": "gub", +"Wayuu": "guc", +"YocobouéDida": "gud", +"Gurinji": "gue", +"Gupapuyngu": "guf", +"ParaguayanGuaraní": "gug", +"Guahibo": "guh", +"EasternBolivian Guaraní": "gui", +"Gujarati": "guj", +"Gumuz": "guk", +"Sea Island Creole English": "gul", +"Guambiano": "gum", +"MbyáGuaraní": "gun", +"Guayabero": "guo", +"Gunwinggu": "gup", +"Aché": "guq", +"Farefare": "gur", +"GuineanSign Language": "gus", +"Maléku Jaíka": "gut", +"Yanomamö": "guu", +"Gun": "guw", +"Gourmanchéma": "gux", +"Gusii": "guz", +"Guana (Paraguay)": "gva", +"Guanano": "gvc", +"Duwet": "gve", +"Golin": "gvf", +"Guajá": "gvj", +"Gulay": "gvl", +"Gurmana": "gvm", +"Kuku-Yalanji": "gvn", +"GaviãoDo Jiparaná": "gvo", +"Pará Gavião": "gvp", +"WesternGurung": "gvr", +"Gumawana": "gvs", +"Guyani": "gvy", +"Mbato": "gwa", +"Gwa": "gwb", +"Kalami": "gwc", +"Gawwada": "gwd", +"Gweno": "gwe", +"Gowro": "gwf", +"Moo": "gwg", +"Gwichʼin": "gwi", +"/Gwi": "gwj", +"Awngthim": "gwm", +"Gwandara": "gwn", +"Gwere": "gwr", +"Gawar-Bati": "gwt", +"Guwamu": "gwu", +"Kwini": "gww", +"Gua": "gwx", +"WèSouthern": "gxx", +"NorthwestGbaya": "gya", +"Garus": "gyb", +"Kayardild": "gyd", +"Gyem": "gye", +"Gungabula": "gyf", +"Gbayi": "gyg", +"Gyele": "gyi", +"Gayil": "gyl", +"Ngäbere": "gym", +"Guyanese Creole English": "gyn", +"Gyalsumdo": "gyo", +"Guarayu": "gyr", +"Gunya": "gyy", +"Geji": "gyz", +"Ganza": "gza", +"Gazi": "gzi", +"Gane": "gzn", +"Han": "haa", +"Hanoi Sign Language": "hab", +"Gurani": "hac", +"Hatam": "had", +"EasternOromo": "hae", +"HaiphongSign Language": "haf", +"Hanga": "hag", +"Hahon": "hah", +"Haida": "hai", +"Hajong": "haj", +"HakkaChinese": "hak", +"Halang": "hal", +"Hewa": "ham", +"Hangaza": "han", +"Hakö": "hao", +"Hupla": "hap", +"Ha": "haq", +"Harari": "har", +"Haisla": "has", +"Haitian": "hat", +"Hausa": "hau", +"Havu": "hav", +"Hawaiian": "haw", +"SouthernHaida": "hax", +"Haya": "hay", +"Hazaragi": "haz", +"Hamba": "hba", +"Huba": "hbb", +"Heiban": "hbn", +"Ancient Hebrew": "hbo", +"Serbo-Croatian": "hbs", +"Habu": "hbu", +"AndamanCreole Hindi": "hca", +"Huichol": "hch", +"NorthernHaida": "hdn", +"Honduras Sign Language": "hds", +"Hadiyya": "hdy", +"Northern Qiandong Miao": "hea", +"Hebrew": "heb", +"Herdé": "hed", +"Helong": "heg", +"Hehe": "heh", +"Heiltsuk": "hei", +"Hemba": "hem", +"Herero": "her", +"Hai//om": "hgm", +"Haigwai": "hgw", +"HoiaHoia": "hhi", +"Kerak": "hhr", +"Hoyahoya": "hhy", +"Lamang": "hia", +"Hibito": "hib", +"Hidatsa": "hid", +"Fiji Hindi": "hif", +"Kamwe": "hig", +"Pamosu": "hih", +"Hinduri": "hii", +"Hijuk": "hij", +"Seit-Kaitetu": "hik", +"Hiligaynon": "hil", +"Hindi": "hin", +"Tsoa": "hio", +"Himarimã": "hir", +"Hittite": "hit", +"Hiw": "hiw", +"Hixkaryána": "hix", +"Haji": "hji", +"Kahe": "hka", +"Hunde": "hke", +"Khah": "hkh", +"Hunjara-KainaKe": "hkk", +"Mel-Khaonh": "hkn", +"HongKong Sign Language": "hks", +"Halia": "hla", +"Halbi": "hlb", +"HalangDoan": "hld", +"Hlersu": "hle", +"NgaLa": "hlt", +"HieroglyphicLuwian": "hlu", +"Southern Mashan Hmong": "hma", +"HumburiSenni Songhay": "hmb", +"CentralHuishui Hmong": "hmc", +"Large Flowery Miao": "hmd", +"Eastern Huishui Hmong": "hme", +"HmongDon": "hmf", +"Southwestern Guiyang Hmong": "hmg", +"SouthwesternHuishui Hmong": "hmh", +"NorthernHuishui Hmong": "hmi", +"Ge": "hmj", +"Maek": "hmk", +"Luopohe Hmong": "hml", +"Central Mashan Hmong": "hmm", +"Hmong": "hmn", +"HiriMotu": "hmo", +"Northern Mashan Hmong": "hmp", +"EasternQiandong Miao": "hmq", +"Hmar": "hmr", +"SouthernQiandong Miao": "hms", +"Hamtai": "hmt", +"Hamap": "hmu", +"HmongDô": "hmv", +"Western Mashan Hmong": "hmw", +"Southern Guiyang Hmong": "hmy", +"Hmong Shua": "hmz", +"Mina(Cameroon)": "hna", +"Southern Hindko": "hnd", +"Chhattisgarhi": "hne", +"Hungu": "hng", +"//Ani": "hnh", +"Hani": "hni", +"HmongNjua": "hnj", +"Hanunoo": "hnn", +"Northern Hindko": "hno", +"CaribbeanHindustani": "hns", +"Hung": "hnu", +"Hoava": "hoa", +"Mari(Madang Province)": "hob", +"Ho": "hoc", +"Holma": "hod", +"Horom": "hoe", +"Hobyót": "hoh", +"Holikachuk": "hoi", +"Hadothi": "hoj", +"Holu": "hol", +"Homa": "hom", +"Holoholo": "hoo", +"Hopi": "hop", +"Horo": "hor", +"Ho Chi Minh City Sign Language": "hos", +"Hote": "hot", +"Hovongan": "hov", +"Honi": "how", +"Holiya": "hoy", +"Hozo": "hoz", +"Hpon": "hpo", +"Hawai'i Pidgin Sign Language": "hps", +"Hrangkhol": "hra", +"Niwer Mil": "hrc", +"Hre": "hre", +"Haruku": "hrk", +"HornedMiao": "hrm", +"Haroi": "hro", +"Nhirrpi": "hrp", +"Hértevin": "hrt", +"Hruso": "hru", +"Croatian": "hrv", +"Warwar Feni": "hrw", +"Hunsrik": "hrx", +"Harzani": "hrz", +"UpperSorbian": "hsb", +"HungarianSign Language": "hsh", +"Hausa Sign Language": "hsl", +"XiangChinese": "hsn", +"Harsusi": "hss", +"Hoti": "hti", +"Minica Huitoto": "hto", +"Hadza": "hts", +"Hitu": "htu", +"MiddleHittite": "htx", +"Huambisa": "hub", +"=/Hua": "huc", +"Huaulu": "hud", +"San Francisco Del Mar Huave": "hue", +"Humene": "huf", +"Huachipaeri": "hug", +"Huilliche": "huh", +"Huli": "hui", +"Northern Guiyang Hmong": "huj", +"Hulung": "huk", +"Hula": "hul", +"Hungana": "hum", +"Hungarian": "hun", +"Hu": "huo", +"Hupa": "hup", +"Tsat": "huq", +"Halkomelem": "hur", +"Huastec": "hus", +"Humla": "hut", +"MuruiHuitoto": "huu", +"San Mateo Del Mar Huave": "huv", +"Hukumina": "huw", +"NüpodeHuitoto": "hux", +"Hulaulá": "huy", +"Hunzib": "huz", +"HaitianVodoun Culture Language": "hvc", +"San Dionisio Del Mar Huave": "hve", +"Haveke": "hvk", +"Sabu": "hvn", +"Santa María Del Mar Huave": "hvv", +"Wané": "hwa", +"Hawai'iCreole English": "hwc", +"Hwana": "hwo", +"Hya": "hya", +"Armenian": "hye", +"Western Armenian": "hyw", +"Iaai": "iai", +"Iatmul": "ian", +"Purari": "iar", +"Iban": "iba", +"Ibibio": "ibb", +"Iwaidja": "ibd", +"Akpes": "ibe", +"Ibanag": "ibg", +"Bih": "ibh", +"Ibaloi": "ibl", +"Agoi": "ibm", +"Ibino": "ibn", +"Igbo": "ibo", +"Ibuoro": "ibr", +"Ibu": "ibu", +"Ibani": "iby", +"Ede Ica": "ica", +"Etkywan": "ich", +"Icelandic Sign Language": "icl", +"Islander Creole English": "icr", +"Idakho-Isukha-Tiriki": "ida", +"Indo-Portuguese": "idb", +"Idon": "idc", +"EdeIdaca": "idd", +"Idere": "ide", +"Idi": "idi", +"Ido": "ido", +"Indri": "idr", +"Idesa": "ids", +"Idaté": "idt", +"Idoma": "idu", +"AmganadIfugao": "ifa", +"BatadIfugao": "ifb", +"Ifè": "ife", +"Ifo": "iff", +"TuwaliIfugao": "ifk", +"Teke-Fuumu": "ifm", +"Mayoyao Ifugao": "ifu", +"Keley-IKallahan": "ify", +"Ebira": "igb", +"Igede": "ige", +"Igana": "igg", +"Igala": "igl", +"Kanggape": "igm", +"Ignaciano": "ign", +"Isebe": "igo", +"Interglossa": "igs", +"Igwe": "igw", +"IhaBased Pidgin": "ihb", +"Ihievbe": "ihi", +"Iha": "ihp", +"Bidhawal": "ihw", +"SichuanYi": "iii", +"Thiin": "iin", +"Izon": "ijc", +"Biseni": "ije", +"EdeIje": "ijj", +"Kalabari": "ijn", +"SoutheastIjo": "ijs", +"Eastern Canadian Inuktitut": "ike", +"Iko": "iki", +"Ika": "ikk", +"Ikulu": "ikl", +"Olulumo-Ikom": "iko", +"Ikpeshi": "ikp", +"Ikaranggal": "ikr", +"Inuit Sign Language": "iks", +"Western Canadian Inuktitut": "ikt", +"Inuktitut": "iku", +"Iku-Gora-Ankwa": "ikv", +"Ikwere": "ikw", +"Ik": "ikx", +"Ikizu": "ikz", +"Ile Ape": "ila", +"Ila": "ilb", +"Interlingue": "ile", +"Garig-Ilgar": "ilg", +"IliTurki": "ili", +"Ilongot": "ilk", +"Iranun (Malaysia)": "ilm", +"Iloko": "ilo", +"Iranun (Philippines)": "ilp", +"International Sign": "ils", +"Ili'uun": "ilu", +"Ilue": "ilv", +"MalaMalasar": "ima", +"Anamgura": "imi", +"Miluk": "iml", +"Imonda": "imn", +"Imbongu": "imo", +"Imroing": "imr", +"Marsian": "ims", +"Milyan": "imy", +"Interlingua (International Auxiliary Language Association)": "ina", +"Inga": "inb", +"Indonesian": "ind", +"Degexit'an": "ing", +"Ingush": "inh", +"JungleInga": "inj", +"IndonesianSign Language": "inl", +"Minaean": "inm", +"Isinai": "inn", +"Inoke-Yate": "ino", +"Iñapari": "inp", +"Indian Sign Language": "ins", +"Intha": "int", +"Ineseño": "inz", +"Inor": "ior", +"Tuma-Irumu": "iou", +"Iowa-Oto": "iow", +"Ipili": "ipi", +"Inupiaq": "ipk", +"Ipiko": "ipo", +"Iquito": "iqu", +"Ikwo": "iqw", +"Iresim": "ire", +"Irarutu": "irh", +"Irigwe": "iri", +"Iraqw": "irk", +"Irántxe": "irn", +"Ir": "irr", +"Irula": "iru", +"Kamberau": "irx", +"Iraya": "iry", +"Isabi": "isa", +"Isconahua": "isc", +"Isnag": "isd", +"ItalianSign Language": "ise", +"IrishSign Language": "isg", +"Esan": "ish", +"Nkem-Nkum": "isi", +"Ishkashimi": "isk", +"Icelandic": "isl", +"Masimasi": "ism", +"Isanzu": "isn", +"Isoko": "iso", +"Israeli Sign Language": "isr", +"Istriot": "ist", +"Isu (Menchum Division)": "isu", +"Italian": "ita", +"BinonganItneg": "itb", +"Southern Tidung": "itd", +"Itene": "ite", +"InlaodItneg": "iti", +"Judeo-Italian": "itk", +"Itelmen": "itl", +"ItuMbon Uzo": "itm", +"Itonama": "ito", +"Iteri": "itr", +"Isekiri": "its", +"MaengItneg": "itt", +"Itawit": "itv", +"Ito": "itw", +"Itik": "itx", +"MoyadanItneg": "ity", +"Itzá": "itz", +"IuMien": "ium", +"Ibatan": "ivb", +"Ivatan": "ivv", +"I-Wak": "iwk", +"Iwam": "iwm", +"Iwur": "iwo", +"Sepik Iwam": "iws", +"Ixcatec": "ixc", +"Ixil": "ixl", +"Iyayu": "iya", +"Mesaka": "iyo", +"Yaka (Congo)": "iyx", +"Ingrian": "izh", +"Izere": "izr", +"Izii": "izz", +"Jamamadí": "jaa", +"Hyam": "jab", +"Popti'": "jac", +"Jahanka": "jad", +"Yabem": "jae", +"Jara": "jaf", +"JahHut": "jah", +"Zazao": "jaj", +"Jakun": "jak", +"Yalahatan": "jal", +"Jamaican Creole English": "jam", +"Jandai": "jan", +"Yanyuwa": "jao", +"Yaqay": "jaq", +"NewCaledonian Javanese": "jas", +"Jakati": "jat", +"Yaur": "jau", +"Javanese": "jav", +"JambiMalay": "jax", +"Yan-nhangu": "jay", +"Jawe": "jaz", +"Judeo-Berber": "jbe", +"Badjiri": "jbi", +"Arandai": "jbj", +"Barikewa": "jbk", +"Bijim": "jbm", +"Nafusi": "jbn", +"Lojban": "jbo", +"Jofotek-Bromnya": "jbr", +"Jabutí": "jbt", +"JukunTakum": "jbu", +"Yawijibaya": "jbw", +"JamaicanCountry Sign Language": "jcs", +"Krymchak": "jct", +"Jad": "jda", +"Jadgali": "jdg", +"Judeo-Tat": "jdt", +"Jebero": "jeb", +"Jerung": "jee", +"Jeh": "jeh", +"Yei": "jei", +"JeriKuo": "jek", +"Yelmek": "jel", +"Dza": "jen", +"Jere": "jer", +"Manem": "jet", +"JonkorBourmataguil": "jeu", +"Ngbee": "jgb", +"Judeo-Georgian": "jge", +"Gwak": "jgk", +"Ngomba": "jgo", +"Jehai": "jhi", +"JhankotSign Language": "jhs", +"Jina": "jia", +"Jibu": "jib", +"Tol": "jic", +"Bu": "jid", +"Jilbe": "jie", +"Djingili": "jig", +"Shangzhai": "jih", +"Jiiddu": "jii", +"Jilim": "jil", +"Jimi (Cameroon)": "jim", +"Jiamao": "jio", +"Guanyinqiao": "jiq", +"Jita": "jit", +"YouleJinuo": "jiu", +"Shuar": "jiv", +"BuyuanJinuo": "jiy", +"Jejueo": "jje", +"Bankal": "jjr", +"Kaera": "jka", +"Mobwa Karen": "jkm", +"Kubo": "jko", +"Paku Karen": "jkp", +"Koro (India)": "jkr", +"Amami Koniya Sign Language": "jks", +"Labir": "jku", +"Ngile": "jle", +"JamaicanSign Language": "jls", +"Dima": "jma", +"Zumbun": "jmb", +"Machame": "jmc", +"Yamdena": "jmd", +"Jimi(Nigeria)": "jmi", +"Jumli": "jml", +"Makuri Naga": "jmn", +"Kamara": "jmr", +"Mashi (Nigeria)": "jms", +"Mouwase": "jmw", +"WesternJuxtlahuaca Mixtec": "jmx", +"Jangshung": "jna", +"Jandavra": "jnd", +"Yangman": "jng", +"Janji": "jni", +"Yemsa": "jnj", +"Rawat": "jnl", +"Jaunsari": "jns", +"Joba": "job", +"Wojenaka": "jod", +"Jogi": "jog", +"Jorá": "jor", +"Jordanian Sign Language": "jos", +"Jowulu": "jow", +"Jewish Palestinian Aramaic": "jpa", +"Japanese": "jpn", +"Judeo-Persian": "jpr", +"Jaqaru": "jqr", +"Jarai": "jra", +"Judeo-Arabic": "jrb", +"Jiru": "jrr", +"Jorto": "jrt", +"Japrería": "jru", +"JapaneseSign Language": "jsl", +"Júma": "jua", +"Wannu": "jub", +"Jurchen": "juc", +"Worodougou": "jud", +"Hõne": "juh", +"Ngadjuri": "jui", +"Wapan": "juk", +"Jirel": "jul", +"Jumjum": "jum", +"Juang": "jun", +"Jiba": "juo", +"Hupdë": "jup", +"Jurúna": "jur", +"Jumla Sign Language": "jus", +"Jutish": "jut", +"Ju": "juu", +"Wãpha": "juw", +"Juray": "juy", +"Javindo": "jvd", +"Caribbean Javanese": "jvn", +"Jwira-Pepesa": "jwi", +"Jiarong": "jya", +"Judeo-Yemeni Arabic": "jye", +"Jaya": "jyy", +"Kara-Kalpak": "kaa", +"Kabyle": "kab", +"Kachin": "kac", +"Kadara": "kad", +"Ketangalan": "kae", +"Katso": "kaf", +"Kajaman": "kag", +"Kara (Central African Republic)": "kah", +"Karekare": "kai", +"Jju": "kaj", +"Kayapa Kallahan": "kak", +"Kalaallisut": "kal", +"Kamba(Kenya)": "kam", +"Kannada": "kan", +"Xaasongaxango": "kao", +"Bezhta": "kap", +"Capanahua": "kaq", +"Kashmiri": "kas", +"Georgian": "kat", +"Kanuri": "kau", +"Katukína": "kav", +"Kawi": "kaw", +"Kao": "kax", +"Kamayurá": "kay", +"Kazakh": "kaz", +"Kalarko": "kba", +"Kaxuiâna": "kbb", +"Kadiwéu": "kbc", +"Kabardian": "kbd", +"Kanju": "kbe", +"Khamba": "kbg", +"Camsá": "kbh", +"Kaptiau": "kbi", +"Kari": "kbj", +"GrassKoiari": "kbk", +"Kanembu": "kbl", +"Iwal": "kbm", +"Kare (Central African Republic)": "kbn", +"Keliko": "kbo", +"Kabiyè": "kbp", +"Kamano": "kbq", +"Kafa": "kbr", +"Kande": "kbs", +"Abadi": "kbt", +"Kabutra": "kbu", +"Dera(Indonesia)": "kbv", +"Kaiep": "kbw", +"Ap Ma": "kbx", +"MangaKanuri": "kby", +"Duhwa": "kbz", +"Khanty": "kca", +"Kawacha": "kcb", +"Lubila": "kcc", +"NgkâlmpwKanum": "kcd", +"Kaivi": "kce", +"Ukaan": "kcf", +"Tyap": "kcg", +"Vono": "kch", +"Kamantan": "kci", +"Kobiana": "kcj", +"Kalanga": "kck", +"Kela (Papua New Guinea)": "kcl", +"Gula(Central African Republic)": "kcm", +"Nubi": "kcn", +"Kinalakna": "kco", +"Kanga": "kcp", +"Kamo": "kcq", +"Katla": "kcr", +"Koenoem": "kcs", +"Kaian": "kct", +"Kami(Tanzania)": "kcu", +"Kete": "kcv", +"Kabwari": "kcw", +"Kachama-Ganjule": "kcx", +"Korandje": "kcy", +"Konongo": "kcz", +"Worimi": "kda", +"Kutu": "kdc", +"Yankunytjatjara": "kdd", +"Makonde": "kde", +"Mamusi": "kdf", +"Seba": "kdg", +"Tem": "kdh", +"Kumam": "kdi", +"Karamojong": "kdj", +"Numee": "kdk", +"Tsikimba": "kdl", +"Kagoma": "kdm", +"Kunda": "kdn", +"Kaningdon-Nindem": "kdp", +"Koch": "kdq", +"Karaim": "kdr", +"Kuy": "kdt", +"Kadaru": "kdu", +"Koneraw": "kdw", +"Kam": "kdx", +"Keder": "kdy", +"Kwaja": "kdz", +"Kabuverdianu": "kea", +"Kélé": "keb", +"Keiga": "kec", +"Kerewe": "ked", +"EasternKeres": "kee", +"Kpessi": "kef", +"Tese": "keg", +"Keak": "keh", +"Kei": "kei", +"Kadar": "kej", +"Kekchí": "kek", +"Kela (Democratic Republic of Congo)": "kel", +"Kemak": "kem", +"Kenyang": "ken", +"Kakwa": "keo", +"Kaikadi": "kep", +"Kamar": "keq", +"Kera": "ker", +"Kugbo": "kes", +"Ket": "ket", +"Akebu": "keu", +"Kanikkaran": "kev", +"WestKewa": "kew", +"Kukna": "kex", +"Kupia": "key", +"Kukele": "kez", +"Kodava": "kfa", +"NorthwesternKolami": "kfb", +"Konda-Dora": "kfc", +"KorraKoraga": "kfd", +"Kota(India)": "kfe", +"Koya": "kff", +"Kudiya": "kfg", +"Kurichiya": "kfh", +"KannadaKurumba": "kfi", +"Kemiehua": "kfj", +"Kinnauri": "kfk", +"Kung": "kfl", +"Khunsari": "kfm", +"Kuk": "kfn", +"Koro(Côte d'Ivoire)": "kfo", +"Korwa": "kfp", +"Korku": "kfq", +"Kachchi": "kfr", +"Bilaspuri": "kfs", +"Kanjari": "kft", +"Katkari": "kfu", +"Kurmukar": "kfv", +"Kharam Naga": "kfw", +"KulluPahari": "kfx", +"Kumaoni": "kfy", +"Koromfé": "kfz", +"Koyaga": "kga", +"Kawe": "kgb", +"Komering": "kge", +"Kube": "kgf", +"Kusunda": "kgg", +"SelangorSign Language": "kgi", +"Gamale Kham": "kgj", +"Kaiwá": "kgk", +"Kunggari": "kgl", +"Karipúna": "kgm", +"Karingani": "kgn", +"Krongo": "kgo", +"Kaingang": "kgp", +"Kamoro": "kgq", +"Abun": "kgr", +"Kumbainggar": "kgs", +"Somyev": "kgt", +"Kobol": "kgu", +"Karas": "kgv", +"KaronDori": "kgw", +"Kamaru": "kgx", +"Kyerung": "kgy", +"Khasi": "kha", +"Lü": "khb", +"Tukang Besi North": "khc", +"Bädi Kanum": "khd", +"Korowai": "khe", +"Khuen": "khf", +"KhamsTibetan": "khg", +"Kehu": "khh", +"Kuturmi": "khj", +"HalhMongolian": "khk", +"Lusi": "khl", +"CentralKhmer": "khm", +"Khandesi": "khn", +"Khotanese": "kho", +"Kapori": "khp", +"KoyraChiini Songhay": "khq", +"Kharia": "khr", +"Kasua": "khs", +"Khamti": "kht", +"Nkhumbi": "khu", +"Khvarshi": "khv", +"Khowar": "khw", +"Kanu": "khx", +"Kele (Democratic Republic of Congo)": "khy", +"Keapara": "khz", +"Kim": "kia", +"Koalib": "kib", +"Kickapoo": "kic", +"Koshin": "kid", +"Kibet": "kie", +"Eastern Parbate Kham": "kif", +"Kimaama": "kig", +"Kilmeri": "kih", +"Kitsai": "kii", +"Kilivila": "kij", +"Kikuyu": "kik", +"Kariya": "kil", +"Karagas": "kim", +"Kinyarwanda": "kin", +"Kiowa": "kio", +"Sheshi Kham": "kip", +"Kosadle": "kiq", +"Kirghiz": "kir", +"Kis": "kis", +"Agob": "kit", +"Kirmanjki (individual language)": "kiu", +"Kimbu": "kiv", +"NortheastKiwai": "kiw", +"KhiamniunganNaga": "kix", +"Kirikiri": "kiy", +"Kisi": "kiz", +"Mlap": "kja", +"Q'anjob'al": "kjb", +"CoastalKonjo": "kjc", +"SouthernKiwai": "kjd", +"Kisar": "kje", +"Khmu": "kjg", +"Khakas": "kjh", +"Zabana": "kji", +"Khinalugh": "kjj", +"Highland Konjo": "kjk", +"WesternParbate Kham": "kjl", +"Kháng": "kjm", +"Kunjen": "kjn", +"HarijanKinnauri": "kjo", +"PwoEastern Karen": "kjp", +"WesternKeres": "kjq", +"Kurudu": "kjr", +"East Kewa": "kjs", +"PhraePwo Karen": "kjt", +"Kashaya": "kju", +"Kaikavian Literary Language": "kjv", +"Ramopa": "kjx", +"Erave": "kjy", +"Bumthangkha": "kjz", +"Kakanda": "kka", +"Kwerisa": "kkb", +"Odoodee": "kkc", +"Kinuku": "kkd", +"Kakabe": "kke", +"KalaktangMonpa": "kkf", +"MabakaValley Kalinga": "kkg", +"Khün": "kkh", +"Kagulu": "kki", +"Kako": "kkj", +"Kokota": "kkk", +"KosarekYale": "kkl", +"Kiong": "kkm", +"Kon Keu": "kkn", +"Karko": "kko", +"Gugubera": "kkp", +"Kaiku": "kkq", +"Kir-Balar": "kkr", +"Giiwo": "kks", +"Koi": "kkt", +"Tumi": "kku", +"Kangean": "kkv", +"Teke-Kukuya": "kkw", +"Kohin": "kkx", +"Guguyimidjir": "kky", +"Kaska": "kkz", +"Klamath-Modoc": "kla", +"Kiliwa": "klb", +"Kolbila": "klc", +"Gamilaraay": "kld", +"Kulung (Nepal)": "kle", +"Kendeje": "klf", +"Tagakaulo": "klg", +"Weliki": "klh", +"Kalumpang": "kli", +"TurkicKhalaj": "klj", +"Kono(Nigeria)": "klk", +"KaganKalagan": "kll", +"Migum": "klm", +"Kalenjin": "kln", +"Kapya": "klo", +"Kamasa": "klp", +"Rumu": "klq", +"Khaling": "klr", +"Kalasha": "kls", +"Nukna": "klt", +"Klao": "klu", +"Maskelynes": "klv", +"Lindu": "klw", +"Koluwawa": "klx", +"Kalao": "kly", +"Kabola": "klz", +"Konni": "kma", +"Kimbundu": "kmb", +"Southern Dong": "kmc", +"MajukayangKalinga": "kmd", +"Bakole": "kme", +"Kare (Papua New Guinea)": "kmf", +"Kâte": "kmg", +"Kalam": "kmh", +"Kami(Nigeria)": "kmi", +"KumarbhagPaharia": "kmj", +"LimosKalinga": "kmk", +"LowerTanudan Kalinga": "kml", +"Kom(India)": "kmm", +"Awtuw": "kmn", +"Kwoma": "kmo", +"Gimme": "kmp", +"Kwama": "kmq", +"NorthernKurdish": "kmr", +"Kamasau": "kms", +"Kemtuik": "kmt", +"Kanite": "kmu", +"KaripúnaCreole French": "kmv", +"Komo(Democratic Republic of Congo)": "kmw", +"Waboda": "kmx", +"Koma": "kmy", +"KhorasaniTurkish": "kmz", +"Dera(Nigeria)": "kna", +"LubuaganKalinga": "knb", +"Central Kanuri": "knc", +"Konda": "knd", +"Kankanaey": "kne", +"Mankanya": "knf", +"Koongo": "kng", +"Kanufi": "kni", +"Western Kanjobal": "knj", +"Kuranko": "knk", +"Keninjal": "knl", +"Kanamarí": "knm", +"Konkani(individual language)": "knn", +"Kono (Sierra Leone)": "kno", +"Kwanja": "knp", +"Kintaq": "knq", +"Kaningra": "knr", +"Kensiu": "kns", +"Panoan Katukína": "knt", +"Kono (Guinea)": "knu", +"Tabo": "knv", +"Kung-Ekoka": "knw", +"Kendayan": "knx", +"Kanyok": "kny", +"Kalamsé": "knz", +"Konomala": "koa", +"Kpati": "koc", +"Kodi": "kod", +"Kacipo-Balesi": "koe", +"Kubi": "kof", +"Cogui": "kog", +"Koyo": "koh", +"Komi-Permyak": "koi", +"Konkani (macrolanguage)": "kok", +"Kol(Papua New Guinea)": "kol", +"Komi": "kom", +"Kongo": "kon", +"Konzo": "koo", +"Kwato": "kop", +"Kota(Gabon)": "koq", +"Korean": "kor", +"Kosraean": "kos", +"Lagwan": "kot", +"Koke": "kou", +"Kudu-Camo": "kov", +"Kugama": "kow", +"Koyukon": "koy", +"Korak": "koz", +"Kutto": "kpa", +"MulluKurumba": "kpb", +"Curripaco": "kpc", +"Koba": "kpd", +"Kpelle": "kpe", +"Komba": "kpf", +"Kapingamarangi": "kpg", +"Kplang": "kph", +"Kofei": "kpi", +"Karajá": "kpj", +"Kpan": "kpk", +"Kpala": "kpl", +"Koho": "kpm", +"Kepkiriwát": "kpn", +"Ikposo": "kpo", +"Korupun-Sela": "kpq", +"Korafe-Yegha": "kpr", +"Tehit": "kps", +"Karata": "kpt", +"Kafoa": "kpu", +"Komi-Zyrian": "kpv", +"Kobon": "kpw", +"Mountain Koiali": "kpx", +"Koryak": "kpy", +"Kupsabiny": "kpz", +"Mum": "kqa", +"Kovai": "kqb", +"Doromu-Koki": "kqc", +"KoySanjaq Surat": "kqd", +"Kalagan": "kqe", +"Kakabai": "kqf", +"Khe": "kqg", +"Kisankasa": "kqh", +"Koitabu": "kqi", +"Koromira": "kqj", +"KotafonGbe": "kqk", +"Kyenele": "kql", +"Khisa": "kqm", +"Kaonde": "kqn", +"Eastern Krahn": "kqo", +"Kimré": "kqp", +"Krenak": "kqq", +"Kimaragang": "kqr", +"NorthernKissi": "kqs", +"KliasRiver Kadazan": "kqt", +"Seroa": "kqu", +"Okolod": "kqv", +"Kandas": "kqw", +"Mser": "kqx", +"Koorete": "kqy", +"Korana": "kqz", +"Kumhali": "kra", +"Karkin": "krb", +"Karachay-Balkar": "krc", +"Kairui-Midiki": "krd", +"Panará": "kre", +"Koro(Vanuatu)": "krf", +"Kurama": "krh", +"Krio": "kri", +"Kinaray-A": "krj", +"Kerek": "krk", +"Karelian": "krl", +"Sapo": "krn", +"Korop": "krp", +"Kru'ng 2": "krr", +"Gbaya (Sudan)": "krs", +"TumariKanuri": "krt", +"Kurukh": "kru", +"Kavet": "krv", +"WesternKrahn": "krw", +"Karon": "krx", +"Kryts": "kry", +"Sota Kanum": "krz", +"Shuwa-Zamani": "ksa", +"Shambala": "ksb", +"Southern Kalinga": "ksc", +"Kuanua": "ksd", +"Kuni": "kse", +"Bafia": "ksf", +"Kusaghe": "ksg", +"Kölsch": "ksh", +"Krisa": "ksi", +"Uare": "ksj", +"Kansa": "ksk", +"Kumalu": "ksl", +"Kumba": "ksm", +"Kasiguranin": "ksn", +"Kofa": "kso", +"Kaba": "ksp", +"Kwaami": "ksq", +"Borong": "ksr", +"SouthernKisi": "kss", +"Winyé": "kst", +"Khamyang": "ksu", +"Kusu": "ksv", +"S'gawKaren": "ksw", +"Kedang": "ksx", +"KhariaThar": "ksy", +"Kodaku": "ksz", +"Katua": "kta", +"Kambaata": "ktb", +"Kholok": "ktc", +"Kokata": "ktd", +"Nubri": "kte", +"Kwami": "ktf", +"Kalkutung": "ktg", +"Karanga": "kth", +"NorthMuyu": "kti", +"Plapo Krumen": "ktj", +"Kaniet": "ktk", +"Koroshi": "ktl", +"Kurti": "ktm", +"Karitiâna": "ktn", +"Kuot": "kto", +"Kaduo": "ktp", +"Katabaga": "ktq", +"South Muyu": "kts", +"Ketum": "ktt", +"Kituba(Democratic Republic of Congo)": "ktu", +"Eastern Katu": "ktv", +"Kato": "ktw", +"Kaxararí": "ktx", +"Kango(Bas-Uélé District)": "kty", +"Ju/'hoan": "ktz", +"Kuanyama": "kua", +"Kutep": "kub", +"Kwinsu": "kuc", +"'Auhelawa": "kud", +"Kuman": "kue", +"WesternKatu": "kuf", +"Kupa": "kug", +"Kushi": "kuh", +"Kuikúro-Kalapálo": "kui", +"Kuria": "kuj", +"Kepo'": "kuk", +"Kulere": "kul", +"Kumyk": "kum", +"Kunama": "kun", +"Kumukio": "kuo", +"Kunimaipa": "kup", +"Karipuna": "kuq", +"Kurdish": "kur", +"Kusaal": "kus", +"Kutenai": "kut", +"Upper Kuskokwim": "kuu", +"Kur": "kuv", +"Kpagua": "kuw", +"Kukatja": "kux", +"Kuuku-Ya'u": "kuy", +"Kunza": "kuz", +"Bagvalal": "kva", +"Kubu": "kvb", +"Kove": "kvc", +"Kui (Indonesia)": "kvd", +"Kalabakan": "kve", +"Kabalai": "kvf", +"Kuni-Boazi": "kvg", +"Komodo": "kvh", +"Kwang": "kvi", +"Psikye": "kvj", +"KoreanSign Language": "kvk", +"BrekKaren": "kvl", +"Kendem": "kvm", +"BorderKuna": "kvn", +"Dobel": "kvo", +"Kompane": "kvp", +"GebaKaren": "kvq", +"Kerinci": "kvr", +"Lahta Karen": "kvt", +"Yinbaw Karen": "kvu", +"Kola": "kvv", +"Wersing": "kvw", +"ParkariKoli": "kvx", +"Yintale Karen": "kvy", +"Tsakwambo": "kvz", +"Dâw": "kwa", +"Kwa": "kwb", +"Likwala": "kwc", +"Kwaio": "kwd", +"Kwerba": "kwe", +"Kwara'ae": "kwf", +"SaraKaba Deme": "kwg", +"Kowiai": "kwh", +"Awa-Cuaiquer": "kwi", +"Kwanga": "kwj", +"Kwakiutl": "kwk", +"Kofyar": "kwl", +"Kwambi": "kwm", +"Kwangali": "kwn", +"Kwomtari": "kwo", +"Kodia": "kwp", +"Kwer": "kwr", +"Kwese": "kws", +"Kwesten": "kwt", +"Kwakum": "kwu", +"SaraKaba Náà": "kwv", +"Kwinti": "kww", +"Khirwar": "kwx", +"San Salvador Kongo": "kwy", +"Kwadi": "kwz", +"Kairiru": "kxa", +"Krobu": "kxb", +"Konso": "kxc", +"Brunei": "kxd", +"ManumanawKaren": "kxf", +"Karo (Ethiopia)": "kxh", +"Keningau Murut": "kxi", +"Kulfa": "kxj", +"ZayeinKaren": "kxk", +"Northern Khmer": "kxm", +"Kanowit-Tanjong Melanau": "kxn", +"Kanoé": "kxo", +"Wadiyara Koli": "kxp", +"SmärkyKanum": "kxq", +"Koro (Papua New Guinea)": "kxr", +"Kangjia": "kxs", +"Koiwat": "kxt", +"Kuvi": "kxv", +"Konai": "kxw", +"Likuba": "kxx", +"Kayong": "kxy", +"Kerewo": "kxz", +"Kwaya": "kya", +"ButbutKalinga": "kyb", +"Kyaka": "kyc", +"Karey": "kyd", +"Krache": "kye", +"Kouya": "kyf", +"Keyagana": "kyg", +"Karok": "kyh", +"Kiput": "kyi", +"Karao": "kyj", +"Kamayo": "kyk", +"Kalapuya": "kyl", +"Kpatili": "kym", +"NorthernBinukidnon": "kyn", +"Kelon": "kyo", +"Kang": "kyp", +"Kenga": "kyq", +"Kuruáya": "kyr", +"BaramKayan": "kys", +"Kayagar": "kyt", +"Western Kayah": "kyu", +"Kayort": "kyv", +"Kudmali": "kyw", +"Rapoisi": "kyx", +"Kambaira": "kyy", +"Kayabí": "kyz", +"WesternKaraboro": "kza", +"Kaibobo": "kzb", +"Bondoukou Kulango": "kzc", +"Kadai": "kzd", +"Kosena": "kze", +"Da'aKaili": "kzf", +"Kikai": "kzg", +"Kelabit": "kzi", +"Kazukuru": "kzk", +"Kayeli": "kzl", +"Kais": "kzm", +"Kokola": "kzn", +"Kaningi": "kzo", +"Kaidipang": "kzp", +"Kaike": "kzq", +"Karang": "kzr", +"SugutDusun": "kzs", +"Kayupulau": "kzu", +"Komyandaret": "kzv", +"Karirí-Xocó": "kzw", +"Kamarian": "kzx", +"Kango (Tshopo District)": "kzy", +"Kalabra": "kzz", +"Southern Subanen": "laa", +"LinearA": "lab", +"Lacandon": "lac", +"Ladino": "lad", +"Pattani": "lae", +"Lafofa": "laf", +"Langi": "lag", +"Lahnda": "lah", +"Lambya": "lai", +"Lango(Uganda)": "laj", +"Laka (Nigeria)": "lak", +"Lalia": "lal", +"Lamba": "lam", +"Laru": "lan", +"Lao": "lao", +"Laka(Chad)": "lap", +"Qabiao": "laq", +"Larteh": "lar", +"Lama (Togo)": "las", +"Latin": "lat", +"Laba": "lau", +"Latvian": "lav", +"Lauje": "law", +"Tiwa": "lax", +"Lama(Myanmar)": "lay", +"Aribwatsa": "laz", +"Label": "lbb", +"Lakkia": "lbc", +"Lak": "lbe", +"Tinani": "lbf", +"Laopang": "lbg", +"La'bi": "lbi", +"Ladakhi": "lbj", +"CentralBontok": "lbk", +"Libon Bikol": "lbl", +"Lodhi": "lbm", +"Lamet": "lbn", +"Laven": "lbo", +"Wampar": "lbq", +"Northern Lorung": "lbr", +"Libyan Sign Language": "lbs", +"Lachi": "lbt", +"Labu": "lbu", +"Lavatbura-Lamusong": "lbv", +"Tolaki": "lbw", +"Lawangan": "lbx", +"Lamu-Lamu": "lby", +"Lardil": "lbz", +"Legenyem": "lcc", +"Lola": "lcd", +"Loncong": "lce", +"Lubu": "lcf", +"Luchazi": "lch", +"Lisela": "lcl", +"Tungag": "lcm", +"WesternLawa": "lcp", +"Luhu": "lcq", +"Lisabata-Nuniali": "lcs", +"Kla-Dan": "lda", +"Idun": "ldb", +"Luri": "ldd", +"Lenyima": "ldg", +"Lamja-Dengsa-Tola": "ldh", +"Laari": "ldi", +"Lemoro": "ldj", +"Leelau": "ldk", +"Kaan": "ldl", +"Landoma": "ldm", +"Láadan": "ldn", +"Loo": "ldo", +"Tso": "ldp", +"Lufu": "ldq", +"Lega-Shabunda": "lea", +"Lala-Bisa": "leb", +"Leco": "lec", +"Lendu": "led", +"Lyélé": "lee", +"Lelemi": "lef", +"Lenje": "leh", +"Lemio": "lei", +"Lengola": "lej", +"Leipon": "lek", +"Lele(Democratic Republic of Congo)": "lel", +"Nomaande": "lem", +"Lenca": "len", +"Leti(Cameroon)": "leo", +"Lepcha": "lep", +"Lembena": "leq", +"Lenkau": "ler", +"Lese": "les", +"Lesing-Gelimi": "let", +"Kara (Papua New Guinea)": "leu", +"Lamma": "lev", +"LedoKaili": "lew", +"Luang": "lex", +"Lemolang": "ley", +"Lezghian": "lez", +"Lefa": "lfa", +"Lingua Franca Nova": "lfn", +"Lungga": "lga", +"Laghu": "lgb", +"Lugbara": "lgg", +"Laghuu": "lgh", +"Lengilu": "lgi", +"Lingarak": "lgk", +"Wala": "lgl", +"Lega-Mwenga": "lgm", +"Opuuo": "lgn", +"Logba": "lgq", +"Lengo": "lgr", +"Pahi": "lgt", +"Longgu": "lgu", +"Ligenza": "lgz", +"Laha (Viet Nam)": "lha", +"Laha(Indonesia)": "lhh", +"LahuShi": "lhi", +"LahulLohar": "lhl", +"Lhomi": "lhm", +"Lahanan": "lhn", +"Lhokpu": "lhp", +"Mlahsö": "lhs", +"Lo-Toga": "lht", +"Lahu": "lhu", +"West-CentralLimba": "lia", +"Likum": "lib", +"Hlai": "lic", +"Nyindrou": "lid", +"Likila": "lie", +"Limbu": "lif", +"Ligbi": "lig", +"Lihir": "lih", +"Ligurian": "lij", +"Lika": "lik", +"Lillooet": "lil", +"Limburgan": "lim", +"Lingala": "lin", +"Liki": "lio", +"Sekpele": "lip", +"Libido": "liq", +"Liberian English": "lir", +"Lisu": "lis", +"Lithuanian": "lit", +"Logorik": "liu", +"Liv": "liv", +"Col": "liw", +"Liabuku": "lix", +"Banda-Bambari": "liy", +"Libinza": "liz", +"Golpa": "lja", +"Rampi": "lje", +"Laiyolo": "lji", +"Li'o": "ljl", +"LampungApi": "ljp", +"Yirandali": "ljw", +"Yuru": "ljx", +"Lakalei": "lka", +"Kabras": "lkb", +"Kucong": "lkc", +"Lakondê": "lkd", +"Kenyi": "lke", +"Lakha": "lkh", +"Laki": "lki", +"Remun": "lkj", +"Laeko-Libuat": "lkl", +"Kalaamaya": "lkm", +"Lakon": "lkn", +"Khayo": "lko", +"Päri": "lkr", +"Kisa": "lks", +"Lakota": "lkt", +"Kungkari": "lku", +"Lokoya": "lky", +"Lala-Roba": "lla", +"Lolo": "llb", +"Lele (Guinea)": "llc", +"Ladin": "lld", +"Lele(Papua New Guinea)": "lle", +"Hermit": "llf", +"Lole": "llg", +"Lamu": "llh", +"Teke-Laali": "lli", +"Ladji Ladji": "llj", +"Lelak": "llk", +"Lilau": "lll", +"Lasalimu": "llm", +"Lele (Chad)": "lln", +"NorthEfate": "llp", +"Lolak": "llq", +"LithuanianSign Language": "lls", +"Lau": "llu", +"Lauan": "llx", +"EastLimba": "lma", +"Merei": "lmb", +"Limilngan": "lmc", +"Lumun": "lmd", +"Pévé": "lme", +"SouthLembata": "lmf", +"Lamogai": "lmg", +"Lambichhong": "lmh", +"Lombi": "lmi", +"WestLembata": "lmj", +"Lamkang": "lmk", +"Hano": "lml", +"Lambadi": "lmn", +"Lombard": "lmo", +"Limbum": "lmp", +"Lamatuka": "lmq", +"Lamalera": "lmr", +"Lamenu": "lmu", +"Lomaiviti": "lmv", +"LakeMiwok": "lmw", +"Laimbue": "lmx", +"Lamboya": "lmy", +"Langbashe": "lna", +"Mbalanhu": "lnb", +"Lundayeh": "lnd", +"Langobardic": "lng", +"Lanoh": "lnh", +"Daantanai'": "lni", +"Leningitij": "lnj", +"SouthCentral Banda": "lnl", +"Langam": "lnm", +"Lorediakarkar": "lnn", +"Lango (Sudan)": "lno", +"Lamnso'": "lns", +"Longuda": "lnu", +"Lanima": "lnw", +"Lonzo": "lnz", +"Loloda": "loa", +"Lobi": "lob", +"Inonhan": "loc", +"Saluan": "loe", +"Logol": "lof", +"Logo": "log", +"Narim": "loh", +"Loma(Côte d'Ivoire)": "loi", +"Lou": "loj", +"Loko": "lok", +"Mongo": "lol", +"Loma (Liberia)": "lom", +"MalawiLomwe": "lon", +"Lombo": "loo", +"Lopa": "lop", +"Lobala": "loq", +"Téén": "lor", +"Loniu": "los", +"Otuho": "lot", +"Louisiana Creole French": "lou", +"Lopi": "lov", +"TampiasLobu": "low", +"Loun": "lox", +"Lowa": "loy", +"Lozi": "loz", +"Lelepa": "lpa", +"Lepki": "lpe", +"LongPhuri Naga": "lpn", +"Lipo": "lpo", +"Lopit": "lpx", +"RaraBakati'": "lra", +"NorthernLuri": "lrc", +"Laurentian": "lre", +"Laragia": "lrg", +"Marachi": "lri", +"Loarki": "lrk", +"Lari": "lrl", +"Marama": "lrm", +"Lorang": "lrn", +"Laro": "lro", +"Southern Lorung": "lrr", +"Larantuka Malay": "lrt", +"Larevat": "lrv", +"Lemerig": "lrz", +"Lasgerdi": "lsa", +"Burundian Sign Language": "lsb", +"Lishana Deni": "lsd", +"Lusengo": "lse", +"Lish": "lsh", +"Lashi": "lsi", +"Latvian Sign Language": "lsl", +"Saamia": "lsm", +"Tibetan Sign Language": "lsn", +"LaosSign Language": "lso", +"Panamanian Sign Language": "lsp", +"Aruop": "lsr", +"Lasi": "lss", +"Trinidad and Tobago Sign Language": "lst", +"Sivia Sign Language": "lsv", +"MauritianSign Language": "lsy", +"LateMiddle Chinese": "ltc", +"Latgalian": "ltg", +"Thur": "lth", +"Leti(Indonesia)": "lti", +"Latundê": "ltn", +"Tsotso": "lto", +"Tachoni": "lts", +"Latu": "ltu", +"Luxembourgish": "ltz", +"Luba-Lulua": "lua", +"Luba-Katanga": "lub", +"Aringa": "luc", +"Ludian": "lud", +"Luvale": "lue", +"Laua": "luf", +"Ganda": "lug", +"Luiseno": "lui", +"Luna": "luj", +"Lunanakha": "luk", +"Olu'bo": "lul", +"Luimbi": "lum", +"Lunda": "lun", +"Luo(Kenya and Tanzania)": "luo", +"Lumbu": "lup", +"Lucumi": "luq", +"Laura": "lur", +"Lushai": "lus", +"Lushootseed": "lut", +"Lumba-Yakkha": "luu", +"Luwati": "luv", +"Luo (Cameroon)": "luw", +"Luyia": "luy", +"SouthernLuri": "luz", +"Maku'a": "lva", +"Lavi": "lvi", +"Lavukaleve": "lvk", +"StandardLatvian": "lvs", +"Levuka": "lvu", +"Lwalu": "lwa", +"LewoEleng": "lwe", +"Wanga": "lwg", +"White Lachi": "lwh", +"EasternLawa": "lwl", +"Laomian": "lwm", +"Luwo": "lwo", +"Malawian Sign Language": "lws", +"Lewotobi": "lwt", +"Lawu": "lwu", +"Lewo": "lww", +"Lakurumau": "lxm", +"Layakha": "lya", +"Lyngngam": "lyg", +"Luyana": "lyn", +"LiteraryChinese": "lzh", +"Litzlitz": "lzl", +"LeinongNaga": "lzn", +"Laz": "lzz", +"San Jerónimo Tecóatl Mazatec": "maa", +"Yutanduchi Mixtec": "mab", +"Madurese": "mad", +"Bo-Rukul": "mae", +"Mafa": "maf", +"Magahi": "mag", +"Marshallese": "mah", +"Maithili": "mai", +"JalapaDe Díaz Mazatec": "maj", +"Makasar": "mak", +"Malayalam": "mal", +"Mam": "mam", +"Mandingo": "man", +"Chiquihuitlán Mazatec": "maq", +"Marathi": "mar", +"Masai": "mas", +"SanFrancisco Matlatzinca": "mat", +"HuautlaMazatec": "mau", +"Sateré-Mawé": "mav", +"Mampruli": "maw", +"North Moluccan Malay": "max", +"CentralMazahua": "maz", +"Higaonon": "mba", +"WesternBukidnon Manobo": "mbb", +"Macushi": "mbc", +"DibabawonManobo": "mbd", +"Molale": "mbe", +"BabaMalay": "mbf", +"Mangseng": "mbh", +"Ilianen Manobo": "mbi", +"Nadëb": "mbj", +"Malol": "mbk", +"Maxakalí": "mbl", +"Ombamba": "mbm", +"Macaguán": "mbn", +"Mbo(Cameroon)": "mbo", +"Malayo": "mbp", +"Maisin": "mbq", +"Nukak Makú": "mbr", +"SaranganiManobo": "mbs", +"MatigsalugManobo": "mbt", +"Mbula-Bwazza": "mbu", +"Mbulungish": "mbv", +"Maring": "mbw", +"Mari (East Sepik Province)": "mbx", +"Memoni": "mby", +"Amoltepec Mixtec": "mbz", +"Maca": "mca", +"Machiguenga": "mcb", +"Bitur": "mcc", +"Sharanahua": "mcd", +"Itundujia Mixtec": "mce", +"Matsés": "mcf", +"Mapoyo": "mcg", +"Maquiritari": "mch", +"Mese": "mci", +"Mvanip": "mcj", +"Mbunda": "mck", +"Macaguaje": "mcl", +"MalaccanCreole Portuguese": "mcm", +"Masana": "mcn", +"Coatlán Mixe": "mco", +"Makaa": "mcp", +"Ese": "mcq", +"Menya": "mcr", +"Mambai": "mcs", +"Mengisa": "mct", +"Cameroon Mambila": "mcu", +"Minanibai": "mcv", +"Mawa (Chad)": "mcw", +"Mpiemo": "mcx", +"SouthWatut": "mcy", +"Mawan": "mcz", +"Mada (Nigeria)": "mda", +"Morigi": "mdb", +"Male(Papua New Guinea)": "mdc", +"Mbum": "mdd", +"Maba(Chad)": "mde", +"Moksha": "mdf", +"Massalat": "mdg", +"Maguindanaon": "mdh", +"Mamvu": "mdi", +"Mangbetu": "mdj", +"Mangbutu": "mdk", +"Maltese Sign Language": "mdl", +"Mayogo": "mdm", +"Mbati": "mdn", +"Mbala": "mdp", +"Mbole": "mdq", +"Mandar": "mdr", +"Maria (Papua New Guinea)": "mds", +"Mbere": "mdt", +"Mboko": "mdu", +"Santa Lucía Monteverde Mixtec": "mdv", +"Mbosi": "mdw", +"Dizin": "mdx", +"Male (Ethiopia)": "mdy", +"Suruí Do Pará": "mdz", +"Menka": "mea", +"Ikobi-Mena": "meb", +"Mara": "mec", +"Melpa": "med", +"Mengen": "mee", +"Megam": "mef", +"Southwestern Tlaxiaco Mixtec": "meh", +"Midob": "mei", +"Meyah": "mej", +"Mekeo": "mek", +"Central Melanau": "mel", +"Mangala": "mem", +"Mende(Sierra Leone)": "men", +"Kedah Malay": "meo", +"Miriwung": "mep", +"Merey": "meq", +"Meru": "mer", +"Masmaje": "mes", +"Mato": "met", +"Motu": "meu", +"Mann": "mev", +"Maaka": "mew", +"Hassaniyya": "mey", +"Menominee": "mez", +"PattaniMalay": "mfa", +"Bangka": "mfb", +"Mba": "mfc", +"Mendankwe-Nkwen": "mfd", +"Morisyen": "mfe", +"Naki": "mff", +"Mixifore": "mfg", +"Matal": "mfh", +"Wandala": "mfi", +"Mefele": "mfj", +"NorthMofu": "mfk", +"Putai": "mfl", +"MarghiSouth": "mfm", +"Cross River Mbembe": "mfn", +"Mbe": "mfo", +"MakassarMalay": "mfp", +"Moba": "mfq", +"Marithiel": "mfr", +"Mexican Sign Language": "mfs", +"Mokerang": "mft", +"Mbwela": "mfu", +"Mandjak": "mfv", +"Mulaha": "mfw", +"Melo": "mfx", +"Mayo": "mfy", +"Mabaan": "mfz", +"Middle Irish (900-1200)": "mga", +"Mararit": "mgb", +"Morokodo": "mgc", +"Moru": "mgd", +"Mango": "mge", +"Maklew": "mgf", +"Mpongmpong": "mgg", +"Makhuwa-Meetto": "mgh", +"Lijili": "mgi", +"Abureni": "mgj", +"Mawes": "mgk", +"Maleu-Kilenge": "mgl", +"Mambae": "mgm", +"Mbangi": "mgn", +"Meta'": "mgo", +"Eastern Magar": "mgp", +"Malila": "mgq", +"Mambwe-Lungu": "mgr", +"Manda (Tanzania)": "mgs", +"Mongol": "mgt", +"Mailu": "mgu", +"Matengo": "mgv", +"Matumbi": "mgw", +"Mbunga": "mgy", +"Mbugwe": "mgz", +"Manda(India)": "mha", +"Mahongwe": "mhb", +"Mocho": "mhc", +"Mbugu": "mhd", +"Besisi": "mhe", +"Mamaa": "mhf", +"Margu": "mhg", +"Ma'di": "mhi", +"Mogholi": "mhj", +"Mungaka": "mhk", +"Mauwake": "mhl", +"Makhuwa-Moniga": "mhm", +"Mócheno": "mhn", +"Mashi(Zambia)": "mho", +"BalineseMalay": "mhp", +"Mandan": "mhq", +"EasternMari": "mhr", +"Buru(Indonesia)": "mhs", +"Mandahuaca": "mht", +"Digaro-Mishmi": "mhu", +"Mbukushu": "mhw", +"Maru": "mhx", +"Ma'anyan": "mhy", +"Mor(Mor Islands)": "mhz", +"Miami": "mia", +"AtatláhucaMixtec": "mib", +"Mi'kmaq": "mic", +"Mandaic": "mid", +"OcotepecMixtec": "mie", +"Mofu-Gudur": "mif", +"San Miguel El Grande Mixtec": "mig", +"ChayucoMixtec": "mih", +"ChigmecatitlánMixtec": "mii", +"Abar": "mij", +"Mikasuki": "mik", +"Peñoles Mixtec": "mil", +"Alacatlatzala Mixtec": "mim", +"Minangkabau": "min", +"Pinotepa Nacional Mixtec": "mio", +"Apasco-ApoalaMixtec": "mip", +"Mískito": "miq", +"IsthmusMixe": "mir", +"Uncoded languages": "mis", +"Southern Puebla Mixtec": "mit", +"CacaloxtepecMixtec": "miu", +"Akoye": "miw", +"MixtepecMixtec": "mix", +"AyutlaMixtec": "miy", +"CoatzospanMixtec": "miz", +"Makalero": "mjb", +"SanJuan Colorado Mixtec": "mjc", +"Northwest Maidu": "mjd", +"Muskum": "mje", +"Tu": "mjg", +"Mwera(Nyasa)": "mjh", +"KimMun": "mji", +"Mawak": "mjj", +"Matukar": "mjk", +"Mandeali": "mjl", +"Medebur": "mjm", +"Ma (Papua New Guinea)": "mjn", +"Malankuravan": "mjo", +"Malapandaram": "mjp", +"Malaryan": "mjq", +"Malavedan": "mjr", +"Miship": "mjs", +"Sauria Paharia": "mjt", +"Manna-Dora": "mju", +"Mannan": "mjv", +"Karbi": "mjw", +"Mahali": "mjx", +"Mahican": "mjy", +"Majhi": "mjz", +"Mbre": "mka", +"MalPaharia": "mkb", +"Siliput": "mkc", +"Macedonian": "mkd", +"Mawchi": "mke", +"Miya": "mkf", +"Mak (China)": "mkg", +"Dhatki": "mki", +"Mokilese": "mkj", +"Byep": "mkk", +"Mokole": "mkl", +"Moklen": "mkm", +"Kupang Malay": "mkn", +"MingangDoso": "mko", +"Moikodi": "mkp", +"BayMiwok": "mkq", +"Malas": "mkr", +"SilacayoapanMixtec": "mks", +"Vamale": "mkt", +"KonyankaManinka": "mku", +"Mafea": "mkv", +"Kituba (Congo)": "mkw", +"Kinamiging Manobo": "mkx", +"EastMakian": "mky", +"Makasae": "mkz", +"Malo": "mla", +"Mbule": "mlb", +"CaoLan": "mlc", +"Manambu": "mle", +"Mal": "mlf", +"Malagasy": "mlg", +"Mape": "mlh", +"Malimpung": "mli", +"Miltu": "mlj", +"Ilwana": "mlk", +"MaluaBay": "mll", +"Mulam": "mlm", +"Malango": "mln", +"Mlomp": "mlo", +"Bargam": "mlp", +"Western Maninkakan": "mlq", +"Vame": "mlr", +"Masalit": "mls", +"Maltese": "mlt", +"To'abaita": "mlu", +"Motlav": "mlv", +"Moloko": "mlw", +"Malfaxal": "mlx", +"Malaynon": "mlz", +"Mama": "mma", +"Momina": "mmb", +"MichoacánMazahua": "mmc", +"Maonan": "mmd", +"Mae": "mme", +"Mundat": "mmf", +"NorthAmbrym": "mmg", +"Mehináku": "mmh", +"Musar": "mmi", +"Majhwar": "mmj", +"Mukha-Dora": "mmk", +"ManMet": "mml", +"Maii": "mmm", +"Mamanwa": "mmn", +"ManggaBuang": "mmo", +"Siawi": "mmp", +"Musak": "mmq", +"WesternXiangxi Miao": "mmr", +"Malalamai": "mmt", +"Mmaala": "mmu", +"Miriti": "mmv", +"Emae": "mmw", +"Madak": "mmx", +"Migaama": "mmy", +"Mabaale": "mmz", +"Mbula": "mna", +"Muna": "mnb", +"Manchu": "mnc", +"Mondé": "mnd", +"Naba": "mne", +"Mundani": "mnf", +"Eastern Mnong": "mng", +"Mono(Democratic Republic of Congo)": "mnh", +"Manipuri": "mni", +"Munji": "mnj", +"Mandinka": "mnk", +"Tiale": "mnl", +"Mapena": "mnm", +"SouthernMnong": "mnn", +"MinBei Chinese": "mnp", +"Minriq": "mnq", +"Mono(USA)": "mnr", +"Mansi": "mns", +"Mer": "mnu", +"Rennell-Bellona": "mnv", +"Mon": "mnw", +"Manikion": "mnx", +"Manyawa": "mny", +"Moni": "mnz", +"Mwan": "moa", +"Mocoví": "moc", +"Mobilian": "mod", +"Montagnais": "moe", +"Mongondow": "mog", +"Mohawk": "moh", +"Mboi": "moi", +"Monzombo": "moj", +"Morori": "mok", +"Mangue": "mom", +"Mongolian": "mon", +"Monom": "moo", +"MopánMaya": "mop", +"Mor (Bomberai Peninsula)": "moq", +"Moro": "mor", +"Mossi": "mos", +"Barí": "mot", +"Mogum": "mou", +"Mohave": "mov", +"Moi(Congo)": "mow", +"Molima": "mox", +"Shekkacho": "moy", +"Mukulu": "moz", +"Mpoto": "mpa", +"Mullukmulluk": "mpb", +"Mangarayi": "mpc", +"Machinere": "mpd", +"Majang": "mpe", +"Marba": "mpg", +"Maung": "mph", +"Mpade": "mpi", +"MartuWangka": "mpj", +"Mbara(Chad)": "mpk", +"MiddleWatut": "mpl", +"Yosondúa Mixtec": "mpm", +"Mindiri": "mpn", +"Miu": "mpo", +"Migabac": "mpp", +"Matís": "mpq", +"Vangunu": "mpr", +"Dadibi": "mps", +"Mian": "mpt", +"Makuráp": "mpu", +"Mungkip": "mpv", +"Mapidian": "mpw", +"Misima-Paneati": "mpx", +"Mapia": "mpy", +"Mpi": "mpz", +"Maba(Indonesia)": "mqa", +"Mbuko": "mqb", +"Mangole": "mqc", +"Matepi": "mqe", +"Momuna": "mqf", +"Kota Bangun Kutai Malay": "mqg", +"TlazoyaltepecMixtec": "mqh", +"Mariri": "mqi", +"Mamasa": "mqj", +"RajahKabunsuwan Manobo": "mqk", +"Mbelime": "mql", +"SouthMarquesan": "mqm", +"Moronene": "mqn", +"Modole": "mqo", +"Manipa": "mqp", +"Minokok": "mqq", +"Mander": "mqr", +"West Makian": "mqs", +"Mok": "mqt", +"Mandari": "mqu", +"Mosimo": "mqv", +"Murupi": "mqw", +"Mamuju": "mqx", +"Manggarai": "mqy", +"Malasanga": "mqz", +"Mlabri": "mra", +"Marino": "mrb", +"Maricopa": "mrc", +"WesternMagar": "mrd", +"Martha'sVineyard Sign Language": "mre", +"Elseng": "mrf", +"Miri": "mrg", +"Mara Chin": "mrh", +"Maori": "mri", +"WesternMari": "mrj", +"Hmwaveke": "mrk", +"Mortlockese": "mrl", +"Merlav": "mrm", +"ChekeHolo": "mrn", +"Mru": "mro", +"Morouas": "mrp", +"NorthMarquesan": "mrq", +"Maria(India)": "mrr", +"Maragus": "mrs", +"Marghi Central": "mrt", +"Mono (Cameroon)": "mru", +"Mangareva": "mrv", +"Maranao": "mrw", +"Maremgi": "mrx", +"Mandaya": "mry", +"Marind": "mrz", +"Malay (macrolanguage)": "msa", +"Masbatenyo": "msb", +"SankaranManinka": "msc", +"Yucatec Maya Sign Language": "msd", +"Musey": "mse", +"Mekwei": "msf", +"Moraid": "msg", +"Masikoro Malagasy": "msh", +"SabahMalay": "msi", +"Ma (Democratic Republic of Congo)": "msj", +"Mansaka": "msk", +"Molof": "msl", +"AgusanManobo": "msm", +"Vurës": "msn", +"Mombum": "mso", +"Maritsauá": "msp", +"Caac": "msq", +"Mongolian Sign Language": "msr", +"WestMasela": "mss", +"Musom": "msu", +"Maslam": "msv", +"Mansoanka": "msw", +"Moresada": "msx", +"Aruamu": "msy", +"Momare": "msz", +"Cotabato Manobo": "mta", +"Anyin Morofo": "mtb", +"Munit": "mtc", +"Mualang": "mtd", +"Mono (Solomon Islands)": "mte", +"Murik (Papua New Guinea)": "mtf", +"Una": "mtg", +"Munggui": "mth", +"Maiwa (Papua New Guinea)": "mti", +"Moskona": "mtj", +"Mbe'": "mtk", +"Montol": "mtl", +"Mator": "mtm", +"Matagalpa": "mtn", +"Totontepec Mixe": "mto", +"WichíLhamtés Nocten": "mtp", +"Muong": "mtq", +"Mewari": "mtr", +"Yora": "mts", +"Mota": "mtt", +"TututepecMixtec": "mtu", +"Asaro'o": "mtv", +"SouthernBinukidnon": "mtw", +"TidaáMixtec": "mtx", +"Nabi": "mty", +"Mundang": "mua", +"Mubi": "mub", +"Mbu'": "muc", +"MednyjAleut": "mud", +"MediaLengua": "mue", +"Musgu": "mug", +"Mündü": "muh", +"Musi": "mui", +"Mabire": "muj", +"Mugom": "muk", +"Multiple languages": "mul", +"Maiwala": "mum", +"Nyong": "muo", +"Malvi": "mup", +"Eastern Xiangxi Miao": "muq", +"Murle": "mur", +"Creek": "mus", +"Western Muria": "mut", +"Yaaku": "muu", +"Muthuvan": "muv", +"Bo-Ung": "mux", +"Muyang": "muy", +"Mursi": "muz", +"Manam": "mva", +"Mattole": "mvb", +"Mamboru": "mvd", +"Marwari(Pakistan)": "mve", +"PeripheralMongolian": "mvf", +"Yucuañe Mixtec": "mvg", +"Mire": "mvh", +"Miyako": "mvi", +"Mekmek": "mvk", +"Mbara (Australia)": "mvl", +"Minaveha": "mvn", +"Marovo": "mvo", +"Duri": "mvp", +"Moere": "mvq", +"Marau": "mvr", +"Massep": "mvs", +"Mpotovoro": "mvt", +"Marfa": "mvu", +"TagalMurut": "mvv", +"Machinga": "mvw", +"Meoswar": "mvx", +"IndusKohistani": "mvy", +"Mesqan": "mvz", +"Mwatebu": "mwa", +"Juwal": "mwb", +"Are": "mwc", +"Mwera(Chimwera)": "mwe", +"Murrinh-Patha": "mwf", +"Aiklep": "mwg", +"Mouk-Aria": "mwh", +"Labo": "mwi", +"Kita Maninkakan": "mwk", +"Mirandese": "mwl", +"Sar": "mwm", +"Nyamwanga": "mwn", +"CentralMaewo": "mwo", +"KalaLagaw Ya": "mwp", +"MünChin": "mwq", +"Marwari": "mwr", +"Mwimbi-Muthambi": "mws", +"Moken": "mwt", +"Mittu": "mwu", +"Mentawai": "mwv", +"Hmong Daw": "mww", +"Moingi": "mwz", +"NorthwestOaxaca Mixtec": "mxa", +"TezoatlánMixtec": "mxb", +"Manyika": "mxc", +"Modang": "mxd", +"Mele-Fila": "mxe", +"Malgbe": "mxf", +"Mbangala": "mxg", +"Mvuba": "mxh", +"Mozarabic": "mxi", +"Miju-Mishmi": "mxj", +"Monumbo": "mxk", +"Maxi Gbe": "mxl", +"Meramera": "mxm", +"Moi(Indonesia)": "mxn", +"Mbowe": "mxo", +"TlahuitoltepecMixe": "mxp", +"Juquila Mixe": "mxq", +"Murik(Malaysia)": "mxr", +"HuitepecMixtec": "mxs", +"JamiltepecMixtec": "mxt", +"Mada(Cameroon)": "mxu", +"MetlatónocMixtec": "mxv", +"Namo": "mxw", +"Mahou": "mxx", +"Southeastern Nochixtlán Mixtec": "mxy", +"CentralMasela": "mxz", +"Burmese": "mya", +"Mbay": "myb", +"Mayeka": "myc", +"Myene": "mye", +"Bambassi": "myf", +"Manta": "myg", +"Makah": "myh", +"Mangayat": "myj", +"MamaraSenoufo": "myk", +"Moma": "myl", +"Me'en": "mym", +"Anfillo": "myo", +"Pirahã": "myp", +"Muniche": "myr", +"Mesmes": "mys", +"Mundurukú": "myu", +"Erzya": "myv", +"Muyuw": "myw", +"Masaaba": "myx", +"Macuna": "myy", +"Classical Mandaic": "myz", +"Santa María Zacatepec Mixtec": "mza", +"Tumzabt": "mzb", +"MadagascarSign Language": "mzc", +"Malimba": "mzd", +"Morawa": "mze", +"Monastic Sign Language": "mzg", +"Wichí Lhamtés Güisnay": "mzh", +"IxcatlánMazatec": "mzi", +"Manya": "mzj", +"NigeriaMambila": "mzk", +"MazatlánMixe": "mzl", +"Mumuye": "mzm", +"Mazanderani": "mzn", +"Matipuhy": "mzo", +"Movima": "mzp", +"MoriAtas": "mzq", +"Marúbo": "mzr", +"Macanese": "mzs", +"Mintil": "mzt", +"Inapang": "mzu", +"Manza": "mzv", +"Deg": "mzw", +"Mawayana": "mzx", +"MozambicanSign Language": "mzy", +"Maiadomu": "mzz", +"Namla": "naa", +"Southern Nambikuára": "nab", +"Narak": "nac", +"Naka'ela": "nae", +"Nabak": "naf", +"NagaPidgin": "nag", +"Nalu": "naj", +"Nakanai": "nak", +"Nalik": "nal", +"Nangikurrunggurr": "nam", +"MinNan Chinese": "nan", +"Naaba": "nao", +"Neapolitan": "nap", +"Nama(Namibia)": "naq", +"Iguta": "nar", +"Naasioi": "nas", +"Hungworo": "nat", +"Nauru": "nau", +"Navajo": "nav", +"Nawuri": "naw", +"Nakwi": "nax", +"Narrinyeri": "nay", +"CoatepecNahuatl": "naz", +"Nyemba": "nba", +"Ndoe": "nbb", +"ChangNaga": "nbc", +"Ngbinda": "nbd", +"KonyakNaga": "nbe", +"Nagarchal": "nbg", +"Ngamo": "nbh", +"MaoNaga": "nbi", +"Ngarinman": "nbj", +"Nake": "nbk", +"SouthNdebele": "nbl", +"NgbakaMa'bo": "nbm", +"Kuri": "nbn", +"Nkukoli": "nbo", +"Nnam": "nbp", +"Nggem": "nbq", +"Numana-Nunku-Gbantu-Numbu": "nbr", +"Namibian Sign Language": "nbs", +"Na": "nbt", +"RongmeiNaga": "nbu", +"Ngamambo": "nbv", +"SouthernNgbandi": "nbw", +"Ningera": "nby", +"Iyo": "nca", +"Central Nicobarese": "ncb", +"Ponam": "ncc", +"Nachering": "ncd", +"Yale": "nce", +"Notsi": "ncf", +"Nisga'a": "ncg", +"Central Huasteca Nahuatl": "nch", +"Classical Nahuatl": "nci", +"Northern Puebla Nahuatl": "ncj", +"Nakara": "nck", +"Michoacán Nahuatl": "ncl", +"Nambo": "ncm", +"Nauna": "ncn", +"Sibe": "nco", +"Northern Katang": "ncq", +"Ncane": "ncr", +"NicaraguanSign Language": "ncs", +"ChotheNaga": "nct", +"Chumburung": "ncu", +"Central Puebla Nahuatl": "ncx", +"Natchez": "ncz", +"Ndasa": "nda", +"Kenswei Nsei": "ndb", +"Ndau": "ndc", +"Nde-Nsele-Nta": "ndd", +"NorthNdebele": "nde", +"Nadruvian": "ndf", +"Ndengereko": "ndg", +"Ndali": "ndh", +"SambaLeko": "ndi", +"Ndamba": "ndj", +"Ndaka": "ndk", +"Ndolo": "ndl", +"Ndam": "ndm", +"Ngundi": "ndn", +"Ndonga": "ndo", +"Ndo": "ndp", +"Ndombe": "ndq", +"Ndoola": "ndr", +"LowGerman": "nds", +"Ndunga": "ndt", +"Dugun": "ndu", +"Ndut": "ndv", +"Ndobo": "ndw", +"Nduga": "ndx", +"Lutos": "ndy", +"Ndogo": "ndz", +"EasternNgad'a": "nea", +"Toura (Côte d'Ivoire)": "neb", +"Nedebang": "nec", +"Nde-Gbite": "ned", +"Kumak": "nee", +"Nefamese": "nef", +"Negidal": "neg", +"Nyenkha": "neh", +"Neo-Hittite": "nei", +"Neko": "nej", +"Neku": "nek", +"Nemi": "nem", +"Nengone": "nen", +"Ná-Meo": "neo", +"Nepali": "nep", +"North Central Mixe": "neq", +"Yahadian": "ner", +"Bhoti Kinnauri": "nes", +"Nete": "net", +"Neo": "neu", +"Nyaheun": "nev", +"Newari": "new", +"Neme": "nex", +"Neyo": "ney", +"Nez Perce": "nez", +"Dhao": "nfa", +"Ahwai": "nfd", +"Ayiwo": "nfl", +"Nafaanra": "nfr", +"Mfumte": "nfu", +"Ngbaka": "nga", +"NorthernNgbandi": "ngb", +"Ngombe (Democratic Republic of Congo)": "ngc", +"Ngando (Central African Republic)": "ngd", +"Ngemba": "nge", +"Ngbaka Manza": "ngg", +"N/u": "ngh", +"Ngizim": "ngi", +"Ngie": "ngj", +"Ngalkbun": "ngk", +"Lomwe": "ngl", +"Ngatik Men's Creole": "ngm", +"Ngwo": "ngn", +"Ngulu": "ngp", +"Ngurimi": "ngq", +"Nanggu": "ngr", +"Gvoko": "ngs", +"Ngeq": "ngt", +"GuerreroNahuatl": "ngu", +"Nagumi": "ngv", +"Ngwaba": "ngw", +"Nggwahyi": "ngx", +"Tibea": "ngy", +"Ngungwel": "ngz", +"Nhanda": "nha", +"Beng": "nhb", +"Tabasco Nahuatl": "nhc", +"Chiripá": "nhd", +"EasternHuasteca Nahuatl": "nhe", +"Nhuwala": "nhf", +"TetelcingoNahuatl": "nhg", +"Nahari": "nhh", +"Zacatlán-Ahuacatlán-Tepetzintla Nahuatl": "nhi", +"Isthmus-CosoleacaqueNahuatl": "nhk", +"MorelosNahuatl": "nhm", +"CentralNahuatl": "nhn", +"Takuu": "nho", +"Isthmus-PajapanNahuatl": "nhp", +"Huaxcaleca Nahuatl": "nhq", +"Naro": "nhr", +"OmetepecNahuatl": "nht", +"Noone": "nhu", +"TemascaltepecNahuatl": "nhv", +"Western Huasteca Nahuatl": "nhw", +"Isthmus-Mecayapan Nahuatl": "nhx", +"NorthernOaxaca Nahuatl": "nhy", +"SantaMaría La Alta Nahuatl": "nhz", +"Nias": "nia", +"Nakama": "nib", +"Ngandi": "nid", +"Niellim": "nie", +"Nek": "nif", +"Ngalakan": "nig", +"Nyiha(Tanzania)": "nih", +"Nii": "nii", +"Ngaju": "nij", +"Southern Nicobarese": "nik", +"Nila": "nil", +"Nilamba": "nim", +"Ninzo": "nin", +"Nganasan": "nio", +"Nandi": "niq", +"Nimboran": "nir", +"Nimi": "nis", +"SoutheasternKolami": "nit", +"Niuean": "niu", +"Gilyak": "niv", +"Nimo": "niw", +"Hema": "nix", +"Ngiti": "niy", +"Ningil": "niz", +"Nzanyi": "nja", +"NocteNaga": "njb", +"NdondeHamba": "njd", +"LothaNaga": "njh", +"Gudanji": "nji", +"Njen": "njj", +"Njalgulgule": "njl", +"Angami Naga": "njm", +"LiangmaiNaga": "njn", +"AoNaga": "njo", +"Njerep": "njr", +"Nisa": "njs", +"Ndyuka-Trio Pidgin": "njt", +"Ngadjunmaya": "nju", +"Kunyi": "njx", +"Njyem": "njy", +"Nyishi": "njz", +"Nkoya": "nka", +"KhoibuNaga": "nkb", +"Nkongho": "nkc", +"Koireng": "nkd", +"Duke": "nke", +"InpuiNaga": "nkf", +"Nekgini": "nkg", +"KhezhaNaga": "nkh", +"ThangalNaga": "nki", +"Nakai": "nkj", +"Nokuku": "nkk", +"Namat": "nkm", +"Nkangala": "nkn", +"Nkonya": "nko", +"Niuatoputapu": "nkp", +"Nkami": "nkq", +"Nukuoro": "nkr", +"North Asmat": "nks", +"Nyika(Tanzania)": "nkt", +"BounaKulango": "nku", +"Nyika (Malawi and Zambia)": "nkv", +"Nkutu": "nkw", +"Nkoroo": "nkx", +"Nkari": "nkz", +"Ngombale": "nla", +"Nalca": "nlc", +"Dutch": "nld", +"EastNyala": "nle", +"Gela": "nlg", +"Grangali": "nli", +"Nyali": "nlj", +"Ninia Yali": "nlk", +"Nihali": "nll", +"Mankiyali": "nlm", +"Ngul": "nlo", +"Lao Naga": "nlq", +"Nchumbulu": "nlu", +"Orizaba Nahuatl": "nlv", +"Walangama": "nlw", +"Nahali": "nlx", +"Nyamal": "nly", +"Nalögo": "nlz", +"Maram Naga": "nma", +"Big Nambas": "nmb", +"Ngam": "nmc", +"Ndumu": "nmd", +"MziemeNaga": "nme", +"TangkhulNaga": "nmf", +"Kwasio": "nmg", +"Monsang Naga": "nmh", +"Nyam": "nmi", +"Ngombe (Central African Republic)": "nmj", +"Namakura": "nmk", +"Ndemli": "nml", +"Manangba": "nmm", +"!Xóõ": "nmn", +"Moyon Naga": "nmo", +"Nimanbur": "nmp", +"Nambya": "nmq", +"Nimbari": "nmr", +"Letemboi": "nms", +"Namonuito": "nmt", +"NortheastMaidu": "nmu", +"Ngamini": "nmv", +"Nimoa": "nmw", +"Nama (Papua New Guinea)": "nmx", +"Namuyi": "nmy", +"Nawdm": "nmz", +"Nyangumarta": "nna", +"Nande": "nnb", +"Nancere": "nnc", +"West Ambae": "nnd", +"Ngandyera": "nne", +"Ngaing": "nnf", +"MaringNaga": "nng", +"Ngiemboon": "nnh", +"NorthNuaulu": "nni", +"Nyangatom": "nnj", +"Nankina": "nnk", +"Northern Rengma Naga": "nnl", +"Namia": "nnm", +"Ngete": "nnn", +"Norwegian Nynorsk": "nno", +"WanchoNaga": "nnp", +"Ngindo": "nnq", +"Narungga": "nnr", +"Nanticoke": "nnt", +"Dwang": "nnu", +"Nugunu (Australia)": "nnv", +"Southern Nuni": "nnw", +"Nyangga": "nny", +"Nda'nda'": "nnz", +"Woun Meu": "noa", +"NorwegianBokmål": "nob", +"Nuk": "noc", +"NorthernThai": "nod", +"Nimadi": "noe", +"Nomane": "nof", +"Nogai": "nog", +"Nomu": "noh", +"Noiri": "noi", +"Nonuya": "noj", +"Nooksack": "nok", +"Nomlaki": "nol", +"Nocamán": "nom", +"Old Norse": "non", +"Numanggang": "nop", +"Ngongo": "noq", +"Norwegian": "nor", +"Eastern Nisu": "nos", +"Nomatsiguenga": "not", +"Ewage-Notu": "nou", +"Novial": "nov", +"Nyambo": "now", +"Noy": "noy", +"Nayi": "noz", +"NarPhu": "npa", +"Nupbikha": "npb", +"Ponyo-Gongwang Naga": "npg", +"PhomNaga": "nph", +"Nepali (individual language)": "npi", +"Southeastern Puebla Nahuatl": "npl", +"Mondropolon": "npn", +"PochuriNaga": "npo", +"Nipsan": "nps", +"PuimeiNaga": "npu", +"Noipx": "npx", +"Napu": "npy", +"SouthernNago": "nqg", +"Kura Ede Nago": "nqk", +"Ngendelengo": "nql", +"Ndom": "nqm", +"Nen": "nqn", +"N'Ko": "nqo", +"Kyan-Karyaw Naga": "nqq", +"Nteng": "nqt", +"Akyaung Ari Naga": "nqy", +"Ngom": "nra", +"Nara": "nrb", +"Noric": "nrc", +"SouthernRengma Naga": "nre", +"Jèrriais": "nrf", +"Narango": "nrg", +"ChokriNaga": "nri", +"Ngarla": "nrk", +"Ngarluma": "nrl", +"Narom": "nrm", +"Norn": "nrn", +"North Picene": "nrp", +"Norra": "nrr", +"Northern Kalapuya": "nrt", +"Narua": "nru", +"Ngurmbur": "nrx", +"Lala": "nrz", +"SangtamNaga": "nsa", +"Lower Nossob": "nsb", +"Nshi": "nsc", +"SouthernNisu": "nsd", +"Nsenga": "nse", +"Northwestern Nisu": "nsf", +"Ngasa": "nsg", +"Ngoshie": "nsh", +"NigerianSign Language": "nsi", +"Naskapi": "nsk", +"Norwegian Sign Language": "nsl", +"SumiNaga": "nsm", +"Nehan": "nsn", +"Pedi": "nso", +"NepaleseSign Language": "nsp", +"Northern Sierra Miwok": "nsq", +"MaritimeSign Language": "nsr", +"Nali": "nss", +"TaseNaga": "nst", +"Sierra Negra Nahuatl": "nsu", +"Southwestern Nisu": "nsv", +"Navut": "nsw", +"Nsongo": "nsx", +"Nasal": "nsy", +"Nisenan": "nsz", +"Northern Tidung": "ntd", +"Nathembo": "nte", +"Ngantangarra": "ntg", +"Natioro": "nti", +"Ngaanyatjarra": "ntj", +"Ikoma-Nata-Isenye": "ntk", +"Nateni": "ntm", +"Ntomba": "nto", +"Northern Tepehuan": "ntp", +"Delo": "ntr", +"Natügu": "ntu", +"Nottoway": "ntw", +"Tangkhul Naga (Myanmar)": "ntx", +"Mantsi": "nty", +"Natanzi": "ntz", +"Yuaga": "nua", +"Nukuini": "nuc", +"Ngala": "nud", +"Ngundu": "nue", +"Nusu": "nuf", +"Nungali": "nug", +"Ndunda": "nuh", +"Ngumbi": "nui", +"Nyole": "nuj", +"Nuu-chah-nulth": "nuk", +"NusaLaut": "nul", +"Niuafo'ou": "num", +"Nung(Myanmar)": "nun", +"Nguôn": "nuo", +"Nupe-Nupe-Tako": "nup", +"Nukumanu": "nuq", +"Nukuria": "nur", +"Nuer": "nus", +"Nung(Viet Nam)": "nut", +"Ngbundu": "nuu", +"Northern Nuni": "nuv", +"Nguluwan": "nuw", +"Mehek": "nux", +"Nunggubuyu": "nuy", +"Tlamacazapa Nahuatl": "nuz", +"Nasarian": "nvh", +"Namiae": "nvm", +"Nyokon": "nvo", +"Nawathinehena": "nwa", +"Nyabwa": "nwb", +"ClassicalNewari": "nwc", +"Ngwe": "nwe", +"Ngayawung": "nwg", +"Southwest Tanna": "nwi", +"Nyamusa-Molo": "nwm", +"Nauo": "nwo", +"Nawaru": "nwr", +"MiddleNewar": "nwx", +"Nottoway-Meherrin": "nwy", +"Nauete": "nxa", +"Ngando (Democratic Republic of Congo)": "nxd", +"Nage": "nxe", +"Ngad'a": "nxg", +"Nindi": "nxi", +"Koki Naga": "nxk", +"SouthNuaulu": "nxl", +"Numidian": "nxm", +"Ngawun": "nxn", +"Ndambomo": "nxo", +"Naxi": "nxq", +"Ninggerum": "nxr", +"Nafri": "nxx", +"Nyanja": "nya", +"Nyangbo": "nyb", +"Nyanga-li": "nyc", +"Nyore": "nyd", +"Nyengo": "nye", +"Giryama": "nyf", +"Nyindu": "nyg", +"Nyigina": "nyh", +"Ama(Sudan)": "nyi", +"Nyanga": "nyj", +"Nyaneka": "nyk", +"Nyeu": "nyl", +"Nyamwezi": "nym", +"Nyankole": "nyn", +"Nyoro": "nyo", +"Nyang'i": "nyp", +"Nayini": "nyq", +"Nyiha(Malawi)": "nyr", +"Nyunga": "nys", +"Nyawaygi": "nyt", +"Nyungwe": "nyu", +"Nyulnyul": "nyv", +"Nyaw": "nyw", +"Nganyaywana": "nyx", +"Nyakyusa-Ngonde": "nyy", +"Tigon Mbembe": "nza", +"Njebi": "nzb", +"Nzadi": "nzd", +"Nzima": "nzi", +"Nzakara": "nzk", +"ZemeNaga": "nzm", +"New Zealand Sign Language": "nzs", +"Teke-Nzikou": "nzu", +"Nzakambay": "nzy", +"NangaDama Dogon": "nzz", +"Orok": "oaa", +"Oroch": "oac", +"Old Aramaic (up to 700 BCE)": "oar", +"OldAvar": "oav", +"Obispeño": "obi", +"Southern Bontok": "obk", +"Oblo": "obl", +"Moabite": "obm", +"OboManobo": "obo", +"OldBurmese": "obr", +"Old Breton": "obt", +"Obulom": "obu", +"Ocaina": "oca", +"OldChinese": "och", +"Occitan(post 1500)": "oci", +"Old Cham": "ocm", +"Old Cornish": "oco", +"Atzingo Matlatzinca": "ocu", +"Odut": "oda", +"Od": "odk", +"OldDutch": "odt", +"Odual": "odu", +"Ofo": "ofo", +"Old Frisian": "ofs", +"Efutop": "ofu", +"Ogbia": "ogb", +"Ogbah": "ogc", +"OldGeorgian": "oge", +"Ogbogolo": "ogg", +"Khana": "ogo", +"Ogbronuagum": "ogu", +"OldHittite": "oht", +"Old Hungarian": "ohu", +"Oirata": "oia", +"Inebu One": "oin", +"Northwestern Ojibwa": "ojb", +"CentralOjibwa": "ojc", +"EasternOjibwa": "ojg", +"Ojibwa": "oji", +"OldJapanese": "ojp", +"SevernOjibwa": "ojs", +"Ontong Java": "ojv", +"WesternOjibwa": "ojw", +"Okanagan": "oka", +"Okobo": "okb", +"Kobo": "okc", +"Okodia": "okd", +"Okpe (Southwestern Edo)": "oke", +"Koko Babangk": "okg", +"Koresh-eRostam": "okh", +"Okiek": "oki", +"Oko-Juwoi": "okj", +"KwamtimOne": "okk", +"Old Kentish Sign Language": "okl", +"Middle Korean (10th-16th cent.)": "okm", +"Oki-No-Erabu": "okn", +"OldKorean (3rd-9th cent.)": "oko", +"Kirike": "okr", +"Oko-Eni-Osayen": "oks", +"Oku": "oku", +"Orokaiva": "okv", +"Okpe(Northwestern Edo)": "okx", +"Old Khmer": "okz", +"Walungge": "ola", +"Mochi": "old", +"Olekha": "ole", +"Olkol": "olk", +"Oloma": "olm", +"Livvi": "olo", +"Olrat": "olr", +"Old Lithuanian": "olt", +"Kuvale": "olu", +"Omaha-Ponca": "oma", +"EastAmbae": "omb", +"Mochica": "omc", +"Omagua": "omg", +"Omi": "omi", +"Omok": "omk", +"Ombo": "oml", +"Minoan": "omn", +"Utarmbung": "omo", +"Old Manipuri": "omp", +"OldMarathi": "omr", +"Omotik": "omt", +"Omurano": "omu", +"SouthTairora": "omw", +"OldMon": "omx", +"Old Malay": "omy", +"Ona": "ona", +"Lingao": "onb", +"Oneida": "one", +"Olo": "ong", +"Onin": "oni", +"Onjob": "onj", +"KaboreOne": "onk", +"Onobasulu": "onn", +"Onondaga": "ono", +"Sartang": "onp", +"NorthernOne": "onr", +"Ono": "ons", +"Ontenu": "ont", +"Unua": "onu", +"OldNubian": "onw", +"OninBased Pidgin": "onx", +"TohonoO'odham": "ood", +"Ong": "oog", +"Önge": "oon", +"Oorlams": "oor", +"OldOssetic": "oos", +"Okpamheri": "opa", +"Kopkaka": "opk", +"Oksapmin": "opm", +"Opao": "opo", +"Opata": "opt", +"Ofayé": "opy", +"Oroha": "ora", +"Orma": "orc", +"Orejón": "ore", +"Oring": "org", +"Oroqen": "orh", +"Oriya": "ori", +"Oromo": "orm", +"OrangKanaq": "orn", +"Orokolo": "oro", +"Oruma": "orr", +"OrangSeletar": "ors", +"AdivasiOriya": "ort", +"Ormuri": "oru", +"OldRussian": "orv", +"OroWin": "orw", +"Oro": "orx", +"Odia": "ory", +"Ormu": "orz", +"Osage": "osa", +"Oscan": "osc", +"Osing": "osi", +"Old Sundanese": "osn", +"Ososo": "oso", +"Old Spanish": "osp", +"Ossetian": "oss", +"Osatu": "ost", +"SouthernOne": "osu", +"OldSaxon": "osx", +"Ottoman Turkish (1500-1928)": "ota", +"OldTibetan": "otb", +"OtDanum": "otd", +"Mezquital Otomi": "ote", +"Oti": "oti", +"Old Turkish": "otk", +"Tilapa Otomi": "otl", +"EasternHighland Otomi": "otm", +"TenangoOtomi": "otn", +"Querétaro Otomi": "otq", +"Otoro": "otr", +"Estado de México Otomi": "ots", +"Temoaya Otomi": "ott", +"Otuke": "otu", +"Ottawa": "otw", +"Texcatepec Otomi": "otx", +"OldTamil": "oty", +"IxtencoOtomi": "otz", +"Tagargrent": "oua", +"Glio-Oubi": "oub", +"Ounge": "oue", +"OldUighur": "oui", +"Ouma": "oum", +"Elfdalian": "ovd", +"Owiniga": "owi", +"OldWelsh": "owl", +"Oy": "oyb", +"Oyda": "oyd", +"Wayampi": "oym", +"Oya'oya": "oyy", +"Koonzime": "ozm", +"Parecís": "pab", +"Pacoh": "pac", +"Paumarí": "pad", +"Pagibete": "pae", +"Paranawát": "paf", +"Pangasinan": "pag", +"Tenharim": "pah", +"Pe": "pai", +"Parakanã": "pak", +"Pahlavi": "pal", +"Pampanga": "pam", +"Panjabi": "pan", +"NorthernPaiute": "pao", +"Papiamento": "pap", +"Parya": "paq", +"Panamint": "par", +"Papasena": "pas", +"Palauan": "pau", +"Pakaásnovos": "pav", +"Pawnee": "paw", +"Pankararé": "pax", +"Pech": "pay", +"Pankararú": "paz", +"Páez": "pbb", +"Patamona": "pbc", +"MezontlaPopoloca": "pbe", +"CoyotepecPopoloca": "pbf", +"Paraujano": "pbg", +"E'ñapa Woromaipu": "pbh", +"Parkwa": "pbi", +"Mak(Nigeria)": "pbl", +"Puebla Mazatec": "pbm", +"Kpasam": "pbn", +"Papel": "pbo", +"Badyara": "pbp", +"Pangwa": "pbr", +"CentralPame": "pbs", +"SouthernPashto": "pbt", +"NorthernPashto": "pbu", +"Pnar": "pbv", +"Pyu": "pby", +"Santa Inés Ahuatempan Popoloca": "pca", +"Pear": "pcb", +"Bouyei": "pcc", +"Picard": "pcd", +"RuchingPalaung": "pce", +"Paliyan": "pcf", +"Paniya": "pcg", +"Pardhan": "pch", +"Duruwa": "pci", +"Parenga": "pcj", +"PaiteChin": "pck", +"Pardhi": "pcl", +"Nigerian Pidgin": "pcm", +"Piti": "pcn", +"Pacahuara": "pcp", +"Pyapun": "pcw", +"Anam": "pda", +"PennsylvaniaGerman": "pdc", +"PaDi": "pdi", +"Podena": "pdn", +"Padoe": "pdo", +"Plautdietsch": "pdt", +"Kayan": "pdu", +"Peranakan Indonesian": "pea", +"Eastern Pomo": "peb", +"Mala (Papua New Guinea)": "ped", +"Taje": "pee", +"Northeastern Pomo": "pef", +"Pengo": "peg", +"Bonan": "peh", +"Chichimeca-Jonaz": "pei", +"NorthernPomo": "pej", +"Penchal": "pek", +"Pekal": "pel", +"Phende": "pem", +"Old Persian (ca. 600-400 B.C.)": "peo", +"Kunja": "pep", +"SouthernPomo": "peq", +"IranianPersian": "pes", +"Pémono": "pev", +"Petats": "pex", +"Petjo": "pey", +"EasternPenan": "pez", +"Pááfang": "pfa", +"Peere": "pfe", +"Pfaelzisch": "pfl", +"Sudanese Creole Arabic": "pga", +"Gāndhārī": "pgd", +"Pangwali": "pgg", +"Pagi": "pgi", +"Rerep": "pgk", +"Primitive Irish": "pgl", +"Paelignian": "pgn", +"Pangseng": "pgs", +"Pagu": "pgu", +"Papua New Guinean Sign Language": "pgz", +"Pa-Hng": "pha", +"Phudagi": "phd", +"Phuong": "phg", +"Phukha": "phh", +"Phake": "phk", +"Phalura": "phl", +"Phimbi": "phm", +"Phoenician": "phn", +"Phunoi": "pho", +"Phana'": "phq", +"Pahari-Potwari": "phr", +"Phu Thai": "pht", +"Phuan": "phu", +"Pahlavani": "phv", +"Phangduwali": "phw", +"Pima Bajo": "pia", +"Yine": "pib", +"Pinji": "pic", +"Piaroa": "pid", +"Piro": "pie", +"Pingelapese": "pif", +"Pisabo": "pig", +"Pitcairn-Norfolk": "pih", +"Pini": "pii", +"Pijao": "pij", +"Yom": "pil", +"Powhatan": "pim", +"Piame": "pin", +"Piapoco": "pio", +"Pero": "pip", +"Piratapuyo": "pir", +"Pijin": "pis", +"PittaPitta": "pit", +"Pintupi-Luritja": "piu", +"Pileni": "piv", +"Pimbwe": "piw", +"Piu": "pix", +"Piya-Kwonci": "piy", +"Pije": "piz", +"Pitjantjatjara": "pjt", +"ArdhamāgadhīPrākrit": "pka", +"Pokomo": "pkb", +"Paekche": "pkc", +"Pak-Tong": "pkg", +"Pankhu": "pkh", +"Pakanha": "pkn", +"Pökoot": "pko", +"Pukapuka": "pkp", +"AttapadyKurumba": "pkr", +"Pakistan Sign Language": "pks", +"Maleng": "pkt", +"Paku": "pku", +"Miani": "pla", +"Polonombauk": "plb", +"CentralPalawano": "plc", +"Polari": "pld", +"Palu'e": "ple", +"Pilagá": "plg", +"Paulohi": "plh", +"Pali": "pli", +"Polci": "plj", +"KohistaniShina": "plk", +"ShwePalaung": "pll", +"Palenquero": "pln", +"OlutaPopoluca": "plo", +"Palaic": "plq", +"PalakaSenoufo": "plr", +"San Marcos Tlalcoyalco Popoloca": "pls", +"PlateauMalagasy": "plt", +"Palikúr": "plu", +"Southwest Palawano": "plv", +"Brooke'sPoint Palawano": "plw", +"Bolyu": "ply", +"Paluan": "plz", +"Paama": "pma", +"Pambia": "pmb", +"Pallanganmiddang": "pmd", +"Pwaamei": "pme", +"Pamona": "pmf", +"Māhārāṣṭri Prākrit": "pmh", +"NorthernPumi": "pmi", +"Southern Pumi": "pmj", +"Pamlico": "pmk", +"LinguaFranca": "pml", +"Pomo": "pmm", +"Pam": "pmn", +"Pom": "pmo", +"Northern Pame": "pmq", +"Paynamar": "pmr", +"Piemontese": "pms", +"Tuamotuan": "pmt", +"PlainsMiwok": "pmw", +"PoumeiNaga": "pmx", +"PapuanMalay": "pmy", +"Southern Pame": "pmz", +"PunanBah-Biau": "pna", +"Western Panjabi": "pnb", +"Pannei": "pnc", +"Mpinda": "pnd", +"Western Penan": "pne", +"Pongu": "png", +"Penrhyn": "pnh", +"Aoheng": "pni", +"Pinjarup": "pnj", +"Paunaka": "pnk", +"Paleni": "pnl", +"PunanBatu 1": "pnm", +"Pinai-Hagahai": "pnn", +"Panobo": "pno", +"Pancana": "pnp", +"Pana(Burkina Faso)": "pnq", +"Panim": "pnr", +"Ponosakan": "pns", +"Pontic": "pnt", +"JiongnaiBunu": "pnu", +"Pinigura": "pnv", +"Panytyima": "pnw", +"Phong-Kniang": "pnx", +"Pinyin": "pny", +"Pana (Central African Republic)": "pnz", +"Poqomam": "poc", +"San Juan Atzingo Popoloca": "poe", +"Poke": "pof", +"Potiguára": "pog", +"Poqomchi'": "poh", +"Highland Popoluca": "poi", +"Pokangá": "pok", +"Polish": "pol", +"SoutheasternPomo": "pom", +"Pohnpeian": "pon", +"CentralPomo": "poo", +"Pwapwa": "pop", +"TexistepecPopoluca": "poq", +"Portuguese": "por", +"Sayula Popoluca": "pos", +"Potawatomi": "pot", +"Upper Guinea Crioulo": "pov", +"San Felipe Otlaltepec Popoloca": "pow", +"Polabian": "pox", +"Pogolo": "poy", +"Papi": "ppe", +"Paipai": "ppi", +"Uma": "ppk", +"Pipil": "ppl", +"Papuma": "ppm", +"Papapana": "ppn", +"Folopa": "ppo", +"Pelende": "ppp", +"Pei": "ppq", +"San Luís Temalacayuca Popoloca": "pps", +"Pare": "ppt", +"Papora": "ppu", +"Pa'a": "pqa", +"Malecite-Passamaquoddy": "pqm", +"Parachi": "prc", +"Parsi-Dari": "prd", +"Principense": "pre", +"Paranan": "prf", +"Prussian": "prg", +"Porohanon": "prh", +"Paicî": "pri", +"Parauk": "prk", +"PeruvianSign Language": "prl", +"Kibiri": "prm", +"Prasuni": "prn", +"Old Provençal (to 1500)": "pro", +"Parsi": "prp", +"AshéninkaPerené": "prq", +"Puri": "prr", +"Dari": "prs", +"Phai": "prt", +"Puragi": "pru", +"Parawen": "prw", +"Purik": "prx", +"ProvidenciaSign Language": "prz", +"Asue Awyu": "psa", +"Persian Sign Language": "psc", +"Plains Indian Sign Language": "psd", +"Central Malay": "pse", +"PenangSign Language": "psg", +"SouthwestPashayi": "psh", +"Southeast Pashayi": "psi", +"Puerto Rican Sign Language": "psl", +"Pauserna": "psm", +"Panasuan": "psn", +"PolishSign Language": "pso", +"PhilippineSign Language": "psp", +"Pasi": "psq", +"Portuguese Sign Language": "psr", +"Kaulong": "pss", +"Central Pashto": "pst", +"Sauraseni Prākrit": "psu", +"Port Sandwich": "psw", +"Piscataway": "psy", +"Pai Tavytera": "pta", +"PataxóHã-Ha-Hãe": "pth", +"Pintiini": "pti", +"Patani": "ptn", +"Zo'é": "pto", +"Patep": "ptp", +"Pattapu": "ptq", +"Piamatsina": "ptr", +"Enrekang": "ptt", +"Bambam": "ptu", +"PortVato": "ptv", +"Pentlatch": "ptw", +"Pathiya": "pty", +"WesternHighland Purepecha": "pua", +"Purum": "pub", +"PunanMerap": "puc", +"Punan Aput": "pud", +"Puelche": "pue", +"Punan Merah": "puf", +"Phuie": "pug", +"Puinave": "pui", +"PunanTubu": "puj", +"Puma": "pum", +"Puoc": "puo", +"Pulabu": "pup", +"Puquina": "puq", +"Puruborá": "pur", +"Pushto": "pus", +"Putoh": "put", +"Punu": "puu", +"Puluwatese": "puw", +"Puare": "pux", +"Purisimeño": "puy", +"Pawaia": "pwa", +"Panawa": "pwb", +"Gapapaiwa": "pwg", +"Patwin": "pwi", +"Molbog": "pwm", +"Paiwan": "pwn", +"PwoWestern Karen": "pwo", +"Powari": "pwr", +"Pwo Northern Karen": "pww", +"QuetzaltepecMixe": "pxm", +"Pye Krumen": "pye", +"Fyam": "pym", +"Poyanáwa": "pyn", +"Paraguayan Sign Language": "pys", +"Puyuma": "pyu", +"Pyu(Myanmar)": "pyx", +"Pyen": "pyy", +"Para Naga": "pzn", +"Quapaw": "qua", +"Huallaga Huánuco Quechua": "qub", +"K'iche'": "quc", +"CalderónHighland Quichua": "qud", +"Quechua": "que", +"Lambayeque Quechua": "quf", +"Chimborazo Highland Quichua": "qug", +"South Bolivian Quechua": "quh", +"Quileute": "qui", +"ChachapoyasQuechua": "quk", +"NorthBolivian Quechua": "qul", +"Sipacapense": "qum", +"Quinault": "qun", +"Southern Pastaza Quechua": "qup", +"Quinqui": "quq", +"Yanahuanca Pasco Quechua": "qur", +"Santiago del Estero Quichua": "qus", +"Sacapulteco": "quv", +"TenaLowland Quichua": "quw", +"YauyosQuechua": "qux", +"AyacuchoQuechua": "quy", +"CuscoQuechua": "quz", +"Ambo-PascoQuechua": "qva", +"Cajamarca Quechua": "qvc", +"Eastern Apurímac Quechua": "qve", +"Huamalíes-Dos de Mayo Huánuco Quechua": "qvh", +"ImbaburaHighland Quichua": "qvi", +"Loja Highland Quichua": "qvj", +"CajatamboNorth Lima Quechua": "qvl", +"Margos-Yarowilca-Lauricocha Quechua": "qvm", +"NorthJunín Quechua": "qvn", +"NapoLowland Quechua": "qvo", +"PacaraosQuechua": "qvp", +"SanMartín Quechua": "qvs", +"Huaylla Wanca Quechua": "qvw", +"Queyu": "qvy", +"Northern Pastaza Quichua": "qvz", +"Corongo Ancash Quechua": "qwa", +"Classical Quechua": "qwc", +"HuaylasAncash Quechua": "qwh", +"Kuman(Russia)": "qwm", +"SihuasAncash Quechua": "qws", +"Kwalhioqua-Tlatskanai": "qwt", +"Chiquián Ancash Quechua": "qxa", +"Chincha Quechua": "qxc", +"Panao Huánuco Quechua": "qxh", +"SalasacaHighland Quichua": "qxl", +"Northern Conchucos Ancash Quechua": "qxn", +"Southern Conchucos Ancash Quechua": "qxo", +"PunoQuechua": "qxp", +"Qashqa'i": "qxq", +"CañarHighland Quichua": "qxr", +"Southern Qiang": "qxs", +"SantaAna de Tusi Pasco Quechua": "qxt", +"Arequipa-La Unión Quechua": "qxu", +"Jauja Wanca Quechua": "qxw", +"Quenya": "qya", +"Quiripi": "qyp", +"Dungmali": "raa", +"Camling": "rab", +"Rasawa": "rac", +"Rade": "rad", +"WesternMeohang": "raf", +"Logooli": "rag", +"Rabha": "rah", +"Ramoaaina": "rai", +"Rajasthani": "raj", +"Tulu-Bohuai": "rak", +"Ralte": "ral", +"Canela": "ram", +"Riantana": "ran", +"Rao": "rao", +"Rapanui": "rap", +"Saam": "raq", +"Rarotongan": "rar", +"Tegali": "ras", +"Razajerdi": "rat", +"Raute": "rau", +"Sampang": "rav", +"Rawang": "raw", +"Rang": "rax", +"Rapa": "ray", +"Rahambuu": "raz", +"RumaiPalaung": "rbb", +"NorthernBontok": "rbk", +"MirayaBikol": "rbl", +"Barababaraba": "rbp", +"Réunion Creole French": "rcf", +"Rudbari": "rdb", +"Rerau": "rea", +"Rembong": "reb", +"RejangKayan": "ree", +"Kara(Tanzania)": "reg", +"Reli": "rei", +"Rejang": "rej", +"Rendille": "rel", +"Remo": "rem", +"Rengao": "ren", +"RerBare": "rer", +"Reshe": "res", +"Retta": "ret", +"Reyesano": "rey", +"Roria": "rga", +"Romano-Greek": "rge", +"Rangkas": "rgk", +"Romagnol": "rgn", +"Resígaro": "rgr", +"SouthernRoglai": "rgs", +"Ringgou": "rgu", +"Rohingya": "rhg", +"Yahang": "rhp", +"Riang(India)": "ria", +"Tarifit": "rif", +"Riang(Myanmar)": "ril", +"Nyaturu": "rim", +"Nungu": "rin", +"Ribun": "rir", +"Ritarungo": "rit", +"Riung": "riu", +"Rajong": "rjg", +"Raji": "rji", +"Rajbanshi": "rjs", +"Kraol": "rka", +"Rikbaktsa": "rkb", +"Rakahanga-Manihiki": "rkh", +"Rakhine": "rki", +"Marka": "rkm", +"Rangpuri": "rkt", +"Arakwal": "rkw", +"Rama": "rma", +"Rembarunga": "rmb", +"Carpathian Romani": "rmc", +"TravellerDanish": "rmd", +"Angloromani": "rme", +"KaloFinnish Romani": "rmf", +"Traveller Norwegian": "rmg", +"Murkim": "rmh", +"Lomavren": "rmi", +"Romkun": "rmk", +"Baltic Romani": "rml", +"Roma": "rmm", +"Balkan Romani": "rmn", +"SinteRomani": "rmo", +"Rempi": "rmp", +"Caló": "rmq", +"RomanianSign Language": "rms", +"Domari": "rmt", +"Tavringer Romani": "rmu", +"Romanova": "rmv", +"WelshRomani": "rmw", +"Romam": "rmx", +"Vlax Romani": "rmy", +"Marma": "rmz", +"Ruund": "rnd", +"Ronga": "rng", +"Ranglong": "rnl", +"Roon": "rnn", +"Rongpo": "rnp", +"Nari Nari": "rnr", +"Rungwa": "rnw", +"Tae'": "rob", +"Cacgia Roglai": "roc", +"Rogo": "rod", +"Ronji": "roe", +"Rombo": "rof", +"NorthernRoglai": "rog", +"Romansh": "roh", +"Romblomanon": "rol", +"Romany": "rom", +"Romanian": "ron", +"Rotokas": "roo", +"Kriol": "rop", +"Rongga": "ror", +"Runga": "rou", +"Dela-Oenale": "row", +"Repanbitip": "rpn", +"Rapting": "rpt", +"Ririo": "rri", +"Waima": "rro", +"Arritinngithigh": "rrt", +"Romano-Serbian": "rsb", +"RussianSign Language": "rsl", +"Miriwoong Sign Language": "rsm", +"Rungtu Chin": "rtc", +"Ratahan": "rth", +"Rotuman": "rtm", +"Yurats": "rts", +"Rathawi": "rtw", +"Gungu": "rub", +"Ruuli": "ruc", +"Rusyn": "rue", +"Luguru": "ruf", +"Roviana": "rug", +"Ruga": "ruh", +"Rufiji": "rui", +"Che": "ruk", +"Rundi": "run", +"IstroRomanian": "ruo", +"Macedo-Romanian": "rup", +"MeglenoRomanian": "ruq", +"Russian": "rus", +"Rutul": "rut", +"LanasLobu": "ruu", +"Mala(Nigeria)": "ruy", +"Ruma": "ruz", +"Rawo": "rwa", +"Rwa": "rwk", +"Ruwila": "rwl", +"Amba (Uganda)": "rwm", +"Rawa": "rwo", +"Marwari(India)": "rwr", +"Ngardi": "rxd", +"Karuwali": "rxw", +"Northern Amami-Oshima": "ryn", +"Yaeyama": "rys", +"Central Okinawan": "ryu", +"Rāziḥī": "rzh", +"Saba": "saa", +"Buglere": "sab", +"Meskwaki": "sac", +"Sandawe": "sad", +"Sabanê": "sae", +"Safaliba": "saf", +"Sango": "sag", +"Yakut": "sah", +"Sahu": "saj", +"Sake": "sak", +"SamaritanAramaic": "sam", +"Sanskrit": "san", +"Sause": "sao", +"Samburu": "saq", +"Saraveca": "sar", +"Sasak": "sas", +"Santali": "sat", +"Saleman": "sau", +"Saafi-Saafi": "sav", +"Sawi": "saw", +"Sa": "sax", +"Saya": "say", +"Saurashtra": "saz", +"Ngambay": "sba", +"Simbo": "sbb", +"Kele (Papua New Guinea)": "sbc", +"SouthernSamo": "sbd", +"Saliba": "sbe", +"Shabo": "sbf", +"Seget": "sbg", +"Sori-Harengan": "sbh", +"Seti": "sbi", +"Surbakhal": "sbj", +"Safwa": "sbk", +"Botolan Sambal": "sbl", +"Sagala": "sbm", +"SindhiBhil": "sbn", +"Sabüm": "sbo", +"Sangu(Tanzania)": "sbp", +"Sileibi": "sbq", +"Sembakung Murut": "sbr", +"Subiya": "sbs", +"Kimki": "sbt", +"StodBhoti": "sbu", +"Sabine": "sbv", +"Simba": "sbw", +"Seberuang": "sbx", +"Soli": "sby", +"Sara Kaba": "sbz", +"Chut": "scb", +"Dongxiang": "sce", +"SanMiguel Creole French": "scf", +"Sanggau": "scg", +"Sakachep": "sch", +"SriLankan Creole Malay": "sci", +"Sadri": "sck", +"Shina": "scl", +"Sicilian": "scn", +"Scots": "sco", +"HelambuSherpa": "scp", +"Sa'och": "scq", +"NorthSlavey": "scs", +"Southern Katang": "sct", +"Shumcho": "scu", +"Sheni": "scv", +"Sha": "scw", +"Sicel": "scx", +"Toraja-Sa'dan": "sda", +"Shabak": "sdb", +"SassareseSardinian": "sdc", +"Surubu": "sde", +"Sarli": "sdf", +"Savi": "sdg", +"Southern Kurdish": "sdh", +"Suundi": "sdj", +"SosKundi": "sdk", +"Saudi Arabian Sign Language": "sdl", +"Gallurese Sardinian": "sdn", +"Bukar-SadungBidayuh": "sdo", +"Sherdukpen": "sdp", +"Semandang": "sdq", +"Oraon Sadri": "sdr", +"Sened": "sds", +"Shuadit": "sdt", +"Sarudu": "sdu", +"SibuMelanau": "sdx", +"Sallands": "sdz", +"Semai": "sea", +"ShempireSenoufo": "seb", +"Sechelt": "sec", +"Sedang": "sed", +"Seneca": "see", +"Cebaara Senoufo": "sef", +"Segeju": "seg", +"Sena": "seh", +"Seri": "sei", +"Sene": "sej", +"Sekani": "sek", +"Selkup": "sel", +"Nanerigé Sénoufo": "sen", +"Suarmin": "seo", +"SìcìtéSénoufo": "sep", +"SenaraSénoufo": "seq", +"Serrano": "ser", +"Koyraboro Senni Songhai": "ses", +"Sentani": "set", +"Serui-Laut": "seu", +"Nyarafolo Senoufo": "sev", +"SewaBay": "sew", +"Secoya": "sey", +"SenthangChin": "sez", +"Langue des signes de Belgique Francophone": "sfb", +"Eastern Subanen": "sfe", +"Small Flowery Miao": "sfm", +"SouthAfrican Sign Language": "sfs", +"Sehwi": "sfw", +"OldIrish (to 900)": "sga", +"Mag-antsiAyta": "sgb", +"Kipsigis": "sgc", +"Surigaonon": "sgd", +"Segai": "sge", +"Swiss-GermanSign Language": "sgg", +"Shughni": "sgh", +"Suga": "sgi", +"Surgujia": "sgj", +"Sangkong": "sgk", +"Singa": "sgm", +"Singpho": "sgp", +"Sangisari": "sgr", +"Samogitian": "sgs", +"Brokpake": "sgt", +"Salas": "sgu", +"Sebat Bet Gurage": "sgw", +"SierraLeone Sign Language": "sgx", +"Sanglechi": "sgy", +"Sursurunga": "sgz", +"Shall-Zwall": "sha", +"Ninam": "shb", +"Sonde": "shc", +"KundalShahi": "shd", +"Sheko": "she", +"Shua": "shg", +"Shoshoni": "shh", +"Tachelhit": "shi", +"Shatt": "shj", +"Shilluk": "shk", +"Shendu": "shl", +"Shahrudi": "shm", +"Shan": "shn", +"Shanga": "sho", +"Shipibo-Conibo": "shp", +"Sala": "shq", +"Shi": "shr", +"Shuswap": "shs", +"Shasta": "sht", +"ChadianArabic": "shu", +"Shehri": "shv", +"Shwai": "shw", +"She": "shx", +"Tachawit": "shy", +"SyenaraSenoufo": "shz", +"AkkalaSami": "sia", +"Sebop": "sib", +"Sidamo": "sid", +"Simaa": "sie", +"Siamou": "sif", +"Paasaal": "sig", +"Zire": "sih", +"ShomPeng": "sii", +"Numbami": "sij", +"Sikiana": "sik", +"Tumulung Sisaala": "sil", +"Mende (Papua New Guinea)": "sim", +"Sinhala": "sin", +"Sikkimese": "sip", +"Sonia": "siq", +"Siri": "sir", +"Siuslaw": "sis", +"Sinagen": "siu", +"Sumariup": "siv", +"Siwai": "siw", +"Sumau": "six", +"Sivandi": "siy", +"Siwi": "siz", +"Epena": "sja", +"Sajau Basap": "sjb", +"KildinSami": "sjd", +"PiteSami": "sje", +"Assangori": "sjg", +"KemiSami": "sjk", +"Sajalong": "sjl", +"Mapun": "sjm", +"Sindarin": "sjn", +"Xibe": "sjo", +"Surjapuri": "sjp", +"Siar-Lak": "sjr", +"SenhajaDe Srair": "sjs", +"TerSami": "sjt", +"Ume Sami": "sju", +"Shawnee": "sjw", +"Skagit": "ska", +"Saek": "skb", +"Sauk": "skc", +"Southern Sierra Miwok": "skd", +"Seke (Vanuatu)": "ske", +"Sakirabiá": "skf", +"SakalavaMalagasy": "skg", +"Sikule": "skh", +"Sika": "ski", +"Seke(Nepal)": "skj", +"Sakam": "skm", +"KolibuganSubanon": "skn", +"Seko Tengah": "sko", +"Sekapan": "skp", +"Sininkere": "skq", +"Seraiki": "skr", +"Maia": "sks", +"Sakata": "skt", +"Sakao": "sku", +"Skou": "skv", +"Skepi Creole Dutch": "skw", +"SekoPadang": "skx", +"Sikaiana": "sky", +"Sekar": "skz", +"Sáliba": "slc", +"Sissala": "sld", +"Sholaga": "sle", +"Swiss-Italian Sign Language": "slf", +"SelungaiMurut": "slg", +"SouthernPuget Sound Salish": "slh", +"Lower Silesian": "sli", +"Salumá": "slj", +"Slovak": "slk", +"Salt-Yui": "sll", +"PangutaranSama": "slm", +"Salinan": "sln", +"Lamaholot": "slp", +"Salchuq": "slq", +"Salar": "slr", +"SingaporeSign Language": "sls", +"Sila": "slt", +"Selaru": "slu", +"Slovenian": "slv", +"Sialum": "slw", +"Salampasu": "slx", +"Selayar": "sly", +"Ma'ya": "slz", +"SouthernSami": "sma", +"Simbari": "smb", +"Som": "smc", +"Sama": "smd", +"Northern Sami": "sme", +"Auwe": "smf", +"Simbali": "smg", +"Samei": "smh", +"LuleSami": "smj", +"Bolinao": "smk", +"CentralSama": "sml", +"Musasa": "smm", +"InariSami": "smn", +"Samoan": "smo", +"Samaritan": "smp", +"Samo": "smq", +"Simeulue": "smr", +"SkoltSami": "sms", +"Simte": "smt", +"Somray": "smu", +"Samvedi": "smv", +"Sumbawa": "smw", +"Samba": "smx", +"Semnani": "smy", +"Simeku": "smz", +"Shona": "sna", +"Sebuyau": "snb", +"Sinaugoro": "snc", +"Sindhi": "snd", +"Bau Bidayuh": "sne", +"Noon": "snf", +"Sanga (Democratic Republic of Congo)": "sng", +"Sensi": "sni", +"RiverainSango": "snj", +"Soninke": "snk", +"Sangil": "snl", +"SouthernMa'di": "snm", +"Siona": "snn", +"Snohomish": "sno", +"Siane": "snp", +"Sangu (Gabon)": "snq", +"Sihan": "snr", +"SouthWest Bay": "sns", +"Senggi": "snu", +"Sa'ban": "snv", +"Selee": "snw", +"Sam": "snx", +"Saniyo-Hiyewe": "sny", +"Sinsauru": "snz", +"Thai Song": "soa", +"Sobei": "sob", +"So(Democratic Republic of Congo)": "soc", +"Songoora": "sod", +"Songomeno": "soe", +"Sogdian": "sog", +"Aka": "soh", +"Sonha": "soi", +"Soi": "soj", +"Sokoro": "sok", +"Solos": "sol", +"Somali": "som", +"Songo": "soo", +"Songe": "sop", +"Kanasi": "soq", +"Somrai": "sor", +"Seeku": "sos", +"SouthernSotho": "sot", +"SouthernThai": "sou", +"Sonsorol": "sov", +"Sowanda": "sow", +"So (Cameroon)": "sox", +"Miyobe": "soy", +"Temi": "soz", +"Spanish": "spa", +"Sepa (Indonesia)": "spb", +"Sapé": "spc", +"Saep": "spd", +"Sepa(Papua New Guinea)": "spe", +"Sian": "spg", +"Saponi": "spi", +"Sengo": "spk", +"Selepet": "spl", +"Sepen": "spm", +"Sanapaná": "spn", +"Spokane": "spo", +"SupyireSenoufo": "spp", +"Loreto-UcayaliSpanish": "spq", +"Saparua": "spr", +"Saposa": "sps", +"SpitiBhoti": "spt", +"Sapuan": "spu", +"Sambalpuri": "spv", +"SouthPicene": "spx", +"Sabaot": "spy", +"Shama-Sambuga": "sqa", +"Shau": "sqh", +"Albanian": "sqi", +"Albanian Sign Language": "sqk", +"Suma": "sqm", +"Susquehannock": "sqn", +"Sorkhei": "sqo", +"Sou": "sqq", +"SiculoArabic": "sqr", +"Sri Lankan Sign Language": "sqs", +"Soqotri": "sqt", +"Squamish": "squ", +"Kufr Qassem Sign Language (KQSL)": "sqx", +"Saruga": "sra", +"Sora": "srb", +"LogudoreseSardinian": "src", +"Sardinian": "srd", +"Sara": "sre", +"Nafi": "srf", +"Sulod": "srg", +"Sarikoli": "srh", +"Siriano": "sri", +"SerudungMurut": "srk", +"Isirawa": "srl", +"Saramaccan": "srm", +"SrananTongo": "srn", +"CampidaneseSardinian": "sro", +"Serbian": "srp", +"Sirionó": "srq", +"Serer": "srr", +"Sarsi": "srs", +"Sauri": "srt", +"Suruí": "sru", +"SouthernSorsoganon": "srv", +"Serua": "srw", +"Sirmauri": "srx", +"Sera": "sry", +"Shahmirzadi": "srz", +"Southern Sama": "ssb", +"Suba-Simbiti": "ssc", +"Siroi": "ssd", +"Balangingi": "sse", +"Thao": "ssf", +"Seimat": "ssg", +"ShihhiArabic": "ssh", +"Sansi": "ssi", +"Sausi": "ssj", +"Sunam": "ssk", +"WesternSisaala": "ssl", +"Semnam": "ssm", +"Waata": "ssn", +"Sissano": "sso", +"Spanish Sign Language": "ssp", +"So'a": "ssq", +"Swiss-French Sign Language": "ssr", +"Sô": "sss", +"Sinasina": "sst", +"Susuami": "ssu", +"SharkBay": "ssv", +"Swati": "ssw", +"Samberigi": "ssx", +"Saho": "ssy", +"Sengseng": "ssz", +"Settla": "sta", +"Northern Subanen": "stb", +"Sentinel": "std", +"Liana-Seti": "ste", +"Seta": "stf", +"Trieng": "stg", +"Shelta": "sth", +"BuloStieng": "sti", +"MatyaSamo": "stj", +"Arammba": "stk", +"Stellingwerfs": "stl", +"Setaman": "stm", +"Owa": "stn", +"Stoney": "sto", +"Southeastern Tepehuan": "stp", +"Saterfriesisch": "stq", +"StraitsSalish": "str", +"Shumashti": "sts", +"BudehStieng": "stt", +"Samtao": "stu", +"Silt'e": "stv", +"Satawalese": "stw", +"Siberian Tatar": "sty", +"Sulka": "sua", +"Suku": "sub", +"WesternSubanon": "suc", +"Suena": "sue", +"Suganga": "sug", +"Suki": "sui", +"Shubi": "suj", +"Sukuma": "suk", +"Sundanese": "sun", +"Bouni": "suo", +"Suri": "suq", +"Mwaghavul": "sur", +"Susu": "sus", +"Subtiaba": "sut", +"Sulung": "suv", +"Sumbwa": "suw", +"Sumerian": "sux", +"Suyá": "suy", +"Sunwar": "suz", +"Svan": "sva", +"Ulau-Suain": "svb", +"Vincentian Creole English": "svc", +"Serili": "sve", +"SlovakianSign Language": "svk", +"Slavomolisano": "svm", +"Savosavo": "svs", +"Skalvian": "svx", +"Swahili(macrolanguage)": "swa", +"MaoreComorian": "swb", +"CongoSwahili": "swc", +"Swedish": "swe", +"Sere": "swf", +"Swabian": "swg", +"Swahili(individual language)": "swh", +"Sui": "swi", +"Sira": "swj", +"Malawi Sena": "swk", +"SwedishSign Language": "swl", +"Samosa": "swm", +"Sawknah": "swn", +"Shanenawa": "swo", +"Suau": "swp", +"Sharwa": "swq", +"Saweru": "swr", +"Seluwasan": "sws", +"Sawila": "swt", +"Suwawa": "swu", +"Shekhawati": "swv", +"Sowa": "sww", +"Suruahá": "swx", +"Sarua": "swy", +"Suba": "sxb", +"Sicanian": "sxc", +"Sighu": "sxe", +"Shixing": "sxg", +"SouthernKalapuya": "sxk", +"Selian": "sxl", +"Samre": "sxm", +"Sangir": "sxn", +"Sorothaptic": "sxo", +"Saaroa": "sxr", +"Sasaru": "sxs", +"Upper Saxon": "sxu", +"SaxweGbe": "sxw", +"Siang": "sya", +"Central Subanen": "syb", +"ClassicalSyriac": "syc", +"Seki": "syi", +"Sukur": "syk", +"Sylheti": "syl", +"MayaSamo": "sym", +"Senaya": "syn", +"Suoy": "syo", +"Syriac": "syr", +"Sinyar": "sys", +"Kagate": "syw", +"Samay": "syx", +"Al-Sayyid Bedouin Sign Language": "syy", +"Semelai": "sza", +"Ngalum": "szb", +"SemaqBeri": "szc", +"Seru": "szd", +"Seze": "sze", +"Sengele": "szg", +"Silesian": "szl", +"Sula": "szn", +"Suabo": "szp", +"Solomon Islands Sign Language": "szs", +"Isu(Fako Division)": "szv", +"Sawai": "szw", +"Sakizaya": "szy", +"Lower Tanana": "taa", +"Tabassaran": "tab", +"LowlandTarahumara": "tac", +"Tause": "tad", +"Tariana": "tae", +"Tapirapé": "taf", +"Tagoi": "tag", +"Tahitian": "tah", +"Eastern Tamang": "taj", +"Tala": "tak", +"Tal": "tal", +"Tamil": "tam", +"Tangale": "tan", +"Yami": "tao", +"Taabwa": "tap", +"Tamasheq": "taq", +"Central Tarahumara": "tar", +"TayBoi": "tas", +"Tatar": "tat", +"Upper Tanana": "tau", +"Tatuyo": "tav", +"Tai": "taw", +"Tamki": "tax", +"Atayal": "tay", +"Tocho": "taz", +"Aikanã": "tba", +"Takia": "tbc", +"KakiAe": "tbd", +"Tanimbili": "tbe", +"Mandara": "tbf", +"NorthTairora": "tbg", +"Thurawal": "tbh", +"Gaam": "tbi", +"Tiang": "tbj", +"Calamian Tagbanwa": "tbk", +"Tboli": "tbl", +"Tagbu": "tbm", +"BarroNegro Tunebo": "tbn", +"Tawala": "tbo", +"Taworta": "tbp", +"Tumtum": "tbr", +"Tanguat": "tbs", +"Tembo (Kitembo)": "tbt", +"Tubar": "tbu", +"Tobo": "tbv", +"Tagbanwa": "tbw", +"Kapin": "tbx", +"Tabaru": "tby", +"Ditammari": "tbz", +"Ticuna": "tca", +"Tanacross": "tcb", +"Datooga": "tcc", +"Tafi": "tcd", +"Southern Tutchone": "tce", +"Malinaltepec Me'phaa": "tcf", +"Tamagario": "tcg", +"Turks And Caicos Creole English": "tch", +"Wára": "tci", +"Tchitchege": "tck", +"Taman (Myanmar)": "tcl", +"Tanahmerah": "tcm", +"Tichurong": "tcn", +"Taungyo": "tco", +"TawrChin": "tcp", +"Kaiy": "tcq", +"TorresStrait Creole": "tcs", +"T'en": "tct", +"SoutheasternTarahumara": "tcu", +"TecpatlánTotonac": "tcw", +"Toda": "tcx", +"Tulu": "tcy", +"ThadoChin": "tcz", +"Tagdal": "tda", +"Panchpargania": "tdb", +"Emberá-Tadó": "tdc", +"Tai Nüa": "tdd", +"Tiranige Diga Dogon": "tde", +"Talieng": "tdf", +"Western Tamang": "tdg", +"Thulung": "tdh", +"Tomadino": "tdi", +"Tajio": "tdj", +"Tambas": "tdk", +"Sur": "tdl", +"Taruma": "tdm", +"Tondano": "tdn", +"Teme": "tdo", +"Tita": "tdq", +"Todrah": "tdr", +"Doutai": "tds", +"TetunDili": "tdt", +"Toro": "tdv", +"Tandroy-Mahafaly Malagasy": "tdx", +"Tadyawan": "tdy", +"Temiar": "tea", +"Tetete": "teb", +"Terik": "tec", +"TepoKrumen": "ted", +"HuehuetlaTepehua": "tee", +"Teressa": "tef", +"Teke-Tege": "teg", +"Tehuelche": "teh", +"Torricelli": "tei", +"Ibali Teke": "tek", +"Telugu": "tel", +"Timne": "tem", +"Tama (Colombia)": "ten", +"Teso": "teo", +"Tepecano": "tep", +"Temein": "teq", +"Tereno": "ter", +"Tengger": "tes", +"Tetum": "tet", +"Soo": "teu", +"Teor": "tev", +"Tewa(USA)": "tew", +"Tennet": "tex", +"Tulishi": "tey", +"Tetserret": "tez", +"TofinGbe": "tfi", +"Tanaina": "tfn", +"Tefaro": "tfo", +"Teribe": "tfr", +"Ternate": "tft", +"Sagalla": "tga", +"Tobilung": "tgb", +"Tigak": "tgc", +"Ciwogai": "tgd", +"Eastern Gorkha Tamang": "tge", +"Chalikha": "tgf", +"TobagonianCreole English": "tgh", +"Lawunuia": "tgi", +"Tagin": "tgj", +"Tajik": "tgk", +"Tagalog": "tgl", +"Tandaganon": "tgn", +"Sudest": "tgo", +"Tangoa": "tgp", +"Tring": "tgq", +"Tareng": "tgr", +"Nume": "tgs", +"CentralTagbanwa": "tgt", +"Tanggu": "tgu", +"Tingui-Boto": "tgv", +"TagwanaSenoufo": "tgw", +"Tagish": "tgx", +"Togoyo": "tgy", +"Tagalaka": "tgz", +"Thai": "tha", +"Thayore": "thd", +"Chitwania Tharu": "the", +"Thangmi": "thf", +"NorthernTarahumara": "thh", +"TaiLong": "thi", +"Tharaka": "thk", +"DangauraTharu": "thl", +"Aheu": "thm", +"Thachanadan": "thn", +"Thompson": "thp", +"KochilaTharu": "thq", +"RanaTharu": "thr", +"Thakali": "ths", +"Tahltan": "tht", +"Thuri": "thu", +"Tahaggart Tamahaq": "thv", +"Tha": "thy", +"TayartTamajeq": "thz", +"Tidikelt Tamazight": "tia", +"Tira": "tic", +"Tifal": "tif", +"Tigre": "tig", +"TimugonMurut": "tih", +"Tiene": "tii", +"Tilung": "tij", +"Tikar": "tik", +"Tillamook": "til", +"Timbe": "tim", +"Tindi": "tin", +"Teop": "tio", +"Trimuris": "tip", +"Tiéfo": "tiq", +"Tigrinya": "tir", +"MasadiitItneg": "tis", +"Tinigua": "tit", +"Adasen": "tiu", +"Tiv": "tiv", +"Tiwi": "tiw", +"SouthernTiwa": "tix", +"Tiruray": "tiy", +"Tai Hongjin": "tiz", +"Tajuasohn": "tja", +"Tunjung": "tjg", +"Northern Tujia": "tji", +"Tjungundji": "tjj", +"Tai Laing": "tjl", +"Timucua": "tjm", +"Tonjon": "tjn", +"Temacine Tamazight": "tjo", +"Tjupany": "tjp", +"SouthernTujia": "tjs", +"Tjurruru": "tju", +"Djabwurrung": "tjw", +"Truká": "tka", +"Buksa": "tkb", +"Tukudede": "tkd", +"Takwane": "tke", +"Tukumanféd": "tkf", +"Tesaka Malagasy": "tkg", +"Tokelau": "tkl", +"Takelma": "tkm", +"Toku-No-Shima": "tkn", +"Tikopia": "tkp", +"Tee": "tkq", +"Tsakhur": "tkr", +"Takestani": "tks", +"Kathoriya Tharu": "tkt", +"UpperNecaxa Totonac": "tku", +"Mur Pano": "tkv", +"Teanu": "tkw", +"Tangko": "tkx", +"Takua": "tkz", +"SouthwesternTepehuan": "tla", +"Tobelo": "tlb", +"Yecuatla Totonac": "tlc", +"Talaud": "tld", +"Telefol": "tlf", +"Tofanma": "tlg", +"Klingon": "tlh", +"Tlingit": "tli", +"Talinga-Bwisi": "tlj", +"Taloki": "tlk", +"Tetela": "tll", +"Tolomako": "tlm", +"Talondo'": "tln", +"Talodi": "tlo", +"Filomena Mata-Coahuitlán Totonac": "tlp", +"TaiLoi": "tlq", +"Talise": "tlr", +"Tambotalo": "tls", +"Teluti": "tlt", +"Tulehu": "tlu", +"Taliabu": "tlv", +"Khehek": "tlx", +"Talysh": "tly", +"Tama(Chad)": "tma", +"Katbol": "tmb", +"Tumak": "tmc", +"Haruai": "tmd", +"Tremembé": "tme", +"Toba-Maskoy": "tmf", +"Ternateño": "tmg", +"Tamashek": "tmh", +"Tutuba": "tmi", +"Samarokena": "tmj", +"NorthwesternTamang": "tmk", +"Tamnim Citak": "tml", +"TaiThanh": "tmm", +"Taman(Indonesia)": "tmn", +"Temoq": "tmo", +"Tumleo": "tmq", +"Jewish Babylonian Aramaic (ca. 200-1200 CE)": "tmr", +"Tima": "tms", +"Tasmate": "tmt", +"Iau": "tmu", +"Tembo(Motembo)": "tmv", +"Temuan": "tmw", +"Tami": "tmy", +"Tamanaku": "tmz", +"Tacana": "tna", +"Western Tunebo": "tnb", +"Tanimuca-Retuarã": "tnc", +"AngosturasTunebo": "tnd", +"Tobanga": "tng", +"Maiani": "tnh", +"Tandia": "tni", +"Kwamera": "tnk", +"Lenakel": "tnl", +"Tabla": "tnm", +"North Tanna": "tnn", +"Toromono": "tno", +"Whitesands": "tnp", +"Taino": "tnq", +"Bedik": "tnr", +"Tenis": "tns", +"Tontemboan": "tnt", +"TayKhang": "tnu", +"Tangchangya": "tnv", +"Tonsawang": "tnw", +"Tanema": "tnx", +"Tongwe": "tny", +"Tonga(Thailand)": "tnz", +"Toba": "tob", +"CoyutlaTotonac": "toc", +"Toma": "tod", +"Gizrra": "tof", +"Tonga(Nyasa)": "tog", +"Gitonga": "toh", +"Tonga (Zambia)": "toi", +"Tojolabal": "toj", +"Tolowa": "tol", +"Tombulu": "tom", +"Tonga(Tonga Islands)": "ton", +"Xicotepec De Juárez Totonac": "too", +"Papantla Totonac": "top", +"Toposa": "toq", +"Togbo-Vara Banda": "tor", +"Highland Totonac": "tos", +"Tho": "tou", +"Upper Taromi": "tov", +"Jemez": "tow", +"Tobian": "tox", +"Topoiyo": "toy", +"To": "toz", +"Taupota": "tpa", +"AzoyúMe'phaa": "tpc", +"Tippera": "tpe", +"Tarpia": "tpf", +"Kula": "tpg", +"TokPisin": "tpi", +"Tapieté": "tpj", +"Tupinikin": "tpk", +"Tlacoapa Me'phaa": "tpl", +"Tampulma": "tpm", +"Tupinambá": "tpn", +"TaiPao": "tpo", +"PisafloresTepehua": "tpp", +"Tukpa": "tpq", +"Tuparí": "tpr", +"TlachichilcoTepehua": "tpt", +"Tampuan": "tpu", +"Tanapag": "tpv", +"Tupí": "tpw", +"AcatepecMe'phaa": "tpx", +"Trumai": "tpy", +"Tinputz": "tpz", +"Tembé": "tqb", +"Lehali": "tql", +"Turumsa": "tqm", +"Tenino": "tqn", +"Toaripi": "tqo", +"Tomoip": "tqp", +"Tunni": "tqq", +"Torona": "tqr", +"WesternTotonac": "tqt", +"Touo": "tqu", +"Tonkawa": "tqw", +"Tirahi": "tra", +"Terebu": "trb", +"Copala Triqui": "trc", +"Turi": "trd", +"EastTarangan": "tre", +"Trinidadian Creole English": "trf", +"LishánDidán": "trg", +"Turaka": "trh", +"Trió": "tri", +"Toram": "trj", +"TravellerScottish": "trl", +"Tregami": "trm", +"Trinitario": "trn", +"TaraoNaga": "tro", +"KokBorok": "trp", +"San Martín Itunyoso Triqui": "trq", +"Taushiro": "trr", +"ChicahuaxtlaTriqui": "trs", +"Tunggare": "trt", +"Turoyo": "tru", +"Taroko": "trv", +"Torwali": "trw", +"Tringgus-Sembaan Bidayuh": "trx", +"Turung": "try", +"Torá": "trz", +"Tsaangi": "tsa", +"Tsamai": "tsb", +"Tswa": "tsc", +"Tsakonian": "tsd", +"TunisianSign Language": "tse", +"Tausug": "tsg", +"Tsuvan": "tsh", +"Tsimshian": "tsi", +"Tshangla": "tsj", +"Tseku": "tsk", +"Ts'ün-Lao": "tsl", +"TurkishSign Language": "tsm", +"Tswana": "tsn", +"Tsonga": "tso", +"NorthernToussian": "tsp", +"ThaiSign Language": "tsq", +"Akei": "tsr", +"TaiwanSign Language": "tss", +"Tondi Songway Kiini": "tst", +"Tsou": "tsu", +"Tsogo": "tsv", +"Tsishingini": "tsw", +"Mubami": "tsx", +"TebulSign Language": "tsy", +"Purepecha": "tsz", +"Tutelo": "tta", +"Gaa": "ttb", +"Tektiteko": "ttc", +"Tauade": "ttd", +"Bwanabwana": "tte", +"Tuotomb": "ttf", +"Tutong": "ttg", +"UpperTa'oih": "tth", +"Tobati": "tti", +"Tooro": "ttj", +"Totoro": "ttk", +"Totela": "ttl", +"NorthernTutchone": "ttm", +"Towei": "ttn", +"LowerTa'oih": "tto", +"Tombelala": "ttp", +"TawallammatTamajaq": "ttq", +"Tera": "ttr", +"NortheasternThai": "tts", +"MuslimTat": "ttt", +"Torau": "ttu", +"Titan": "ttv", +"LongWat": "ttw", +"Sikaritai": "tty", +"Tsum": "ttz", +"Wiarumus": "tua", +"Tübatulabal": "tub", +"Mutu": "tuc", +"Tuxá": "tud", +"Tuyuca": "tue", +"CentralTunebo": "tuf", +"Tunia": "tug", +"Taulil": "tuh", +"Tupuri": "tui", +"Tugutil": "tuj", +"Turkmen": "tuk", +"Tula": "tul", +"Tumbuka": "tum", +"Tunica": "tun", +"Tucano": "tuo", +"Tedaga": "tuq", +"Turkish": "tur", +"Tuscarora": "tus", +"Tututni": "tuu", +"Turkana": "tuv", +"Tuxináwa": "tux", +"Tugen": "tuy", +"Turka": "tuz", +"Vaghua": "tva", +"Tsuvadi": "tvd", +"Te'un": "tve", +"SoutheastAmbrym": "tvk", +"Tuvalu": "tvl", +"Tela-Masbuar": "tvm", +"Tavoyan": "tvn", +"Tidore": "tvo", +"Taveta": "tvs", +"Tutsa Naga": "tvt", +"Tunen": "tvu", +"Sedoa": "tvw", +"Taivoan": "tvx", +"Timor Pidgin": "tvy", +"Twana": "twa", +"WesternTawbuid": "twb", +"Teshenawa": "twc", +"Twents": "twd", +"Tewa (Indonesia)": "twe", +"NorthernTiwa": "twf", +"Tereweng": "twg", +"TaiDón": "twh", +"Twi": "twi", +"Tawara": "twl", +"TawangMonpa": "twm", +"Twendi": "twn", +"Tswapong": "two", +"Ere": "twp", +"Tasawaq": "twq", +"Southwestern Tarahumara": "twr", +"Turiwára": "twt", +"Termanu": "twu", +"Tuwari": "tww", +"Tewe": "twx", +"Tawoyan": "twy", +"Tombonuo": "txa", +"TokharianB": "txb", +"Tsetsaut": "txc", +"Totoli": "txe", +"Tangut": "txg", +"Thracian": "txh", +"Ikpeng": "txi", +"Tarjumo": "txj", +"Tomini": "txm", +"WestTarangan": "txn", +"Toto": "txo", +"Tii": "txq", +"Tartessian": "txr", +"Tonsea": "txs", +"Citak": "txt", +"Kayapó": "txu", +"Tatana": "txx", +"TanosyMalagasy": "txy", +"Tauya": "tya", +"Kyenga": "tye", +"O'du": "tyh", +"Teke-Tsaayi": "tyi", +"TaiDo": "tyj", +"Thu Lao": "tyl", +"Kombai": "tyn", +"Thaypan": "typ", +"TaiDaeng": "tyr", +"TàySa Pa": "tys", +"TàyTac": "tyt", +"Kua": "tyu", +"Tuvinian": "tyv", +"Teke-Tyee": "tyx", +"Tiyaa": "tyy", +"Tày": "tyz", +"Tanzanian Sign Language": "tza", +"Tzeltal": "tzh", +"Tz'utujil": "tzj", +"Talossan": "tzl", +"Central Atlas Tamazight": "tzm", +"Tugun": "tzn", +"Tzotzil": "tzo", +"Tabriak": "tzx", +"Uamué": "uam", +"Kuan": "uan", +"Tairuma": "uar", +"Ubang": "uba", +"Ubi": "ubi", +"Buhi'nonBikol": "ubl", +"Ubir": "ubr", +"Umbu-Ungu": "ubu", +"Ubykh": "uby", +"Uda": "uda", +"Udihe": "ude", +"Muduga": "udg", +"Udi": "udi", +"Ujir": "udj", +"Wuzlam": "udl", +"Udmurt": "udm", +"Uduk": "udu", +"Kioko": "ues", +"Ufim": "ufi", +"Ugaritic": "uga", +"Kuku-Ugbanh": "ugb", +"Ughele": "uge", +"Ugandan Sign Language": "ugn", +"Ugong": "ugo", +"UruguayanSign Language": "ugy", +"Uhami": "uha", +"Damal": "uhn", +"Uighur": "uig", +"Uisai": "uis", +"Iyive": "uiv", +"Tanjijili": "uji", +"Kaburi": "uka", +"Ukuriguma": "ukg", +"Ukhwejo": "ukh", +"Kui (India)": "uki", +"Muak Sa-aak": "ukk", +"Ukrainian Sign Language": "ukl", +"Ukpe-Bayobiri": "ukp", +"Ukwa": "ukq", +"Ukrainian": "ukr", +"Urubú-Kaapor Sign Language": "uks", +"Ukue": "uku", +"Kuku": "ukv", +"Ukwuani-Aboh-Ndoni": "ukw", +"Kuuk-Yak": "uky", +"Fungwa": "ula", +"Ulukwumi": "ulb", +"Ulch": "ulc", +"Lule": "ule", +"Usku": "ulf", +"Ulithian": "uli", +"Meriam": "ulk", +"Ullatan": "ull", +"Ulumanda'": "ulm", +"Unserdeutsch": "uln", +"Uma'Lung": "ulu", +"Ulwa": "ulw", +"Umatilla": "uma", +"Umbundu": "umb", +"Marrucinian": "umc", +"Umbindhamu": "umd", +"Umbuygamu": "umg", +"Ukit": "umi", +"Umon": "umm", +"MakyanNaga": "umn", +"Umotína": "umo", +"Umpila": "ump", +"Umbugarla": "umr", +"Pendau": "ums", +"Munsee": "umu", +"NorthWatut": "una", +"Undetermined": "und", +"Uneme": "une", +"Ngarinyin": "ung", +"Uni": "uni", +"Enawené-Nawé": "unk", +"Unami": "unm", +"Kurnai": "unn", +"Mundari": "unr", +"Unubahe": "unu", +"Munda": "unx", +"UndeKaili": "unz", +"Umeda": "upi", +"Uripiv-Wala-Rano-Atchin": "upv", +"Urarina": "ura", +"Urubú-Kaapor": "urb", +"Urningangg": "urc", +"Urdu": "urd", +"Uru": "ure", +"Uradhi": "urf", +"Urigina": "urg", +"Urhobo": "urh", +"Urim": "uri", +"Urak Lawoi'": "urk", +"Urali": "url", +"Urapmin": "urm", +"Uruangnirin": "urn", +"Ura (Papua New Guinea)": "uro", +"Uru-Pa-In": "urp", +"Lehalurup": "urr", +"Urat": "urt", +"Urumi": "uru", +"Uruava": "urv", +"Sop": "urw", +"Urimo": "urx", +"Orya": "ury", +"Uru-Eu-Wau-Wau": "urz", +"Usarufa": "usa", +"Ushojo": "ush", +"Usui": "usi", +"Usaghade": "usk", +"Uspanteco": "usp", +"us-Saare": "uss", +"Uya": "usu", +"Otank": "uta", +"Ute-SouthernPaiute": "ute", +"ut-Hun": "uth", +"Amba(Solomon Islands)": "utp", +"Etulo": "utr", +"Utu": "utu", +"Urum": "uum", +"Kulon-Pazeh": "uun", +"Ura(Vanuatu)": "uur", +"U": "uuu", +"West Uvean": "uve", +"Uri": "uvh", +"Lote": "uvl", +"Kuku-Uwanh": "uwa", +"Doko-Uyanga": "uya", +"Uzbek": "uzb", +"Northern Uzbek": "uzn", +"SouthernUzbek": "uzs", +"Vaagri Booli": "vaa", +"Vale": "vae", +"Vafsi": "vaf", +"Vagla": "vag", +"Varhadi-Nagpuri": "vah", +"Vai": "vai", +"Vasekela Bushman": "vaj", +"Vehes": "val", +"Vanimo": "vam", +"Valman": "van", +"Vao": "vao", +"Vaiphei": "vap", +"Huarijio": "var", +"Vasavi": "vas", +"Vanuma": "vau", +"Varli": "vav", +"Wayu": "vay", +"Southeast Babar": "vbb", +"SouthwesternBontok": "vbk", +"Venetian": "vec", +"Veddah": "ved", +"Veluws": "vel", +"Vemgo-Mabas": "vem", +"Venda": "ven", +"Ventureño": "veo", +"Veps": "vep", +"MomJango": "ver", +"Vaghri": "vgr", +"VlaamseGebarentaal": "vgt", +"Virgin Islands Creole English": "vic", +"Vidunda": "vid", +"Vietnamese": "vie", +"Vili": "vif", +"Viemo": "vig", +"Vilela": "vil", +"Vinza": "vin", +"Vishavan": "vis", +"Viti": "vit", +"Iduna": "viv", +"Kariyarra": "vka", +"Kujarge": "vkj", +"Kaur": "vkk", +"Kulisusu": "vkl", +"Kamakan": "vkm", +"Koro Nulu": "vkn", +"Kodeoha": "vko", +"Korlai Creole Portuguese": "vkp", +"TenggarongKutai Malay": "vkt", +"Kurrama": "vku", +"Koro Zuba": "vkz", +"Valpei": "vlp", +"Vlaams": "vls", +"Martuyhunira": "vma", +"Mbabaram": "vmb", +"JuxtlahuacaMixtec": "vmc", +"MuduKoraga": "vmd", +"East Masela": "vme", +"Mainfränkisch": "vmf", +"Minigir": "vmg", +"Maraghei": "vmh", +"Miwa": "vmi", +"IxtayutlaMixtec": "vmj", +"Makhuwa-Shirima": "vmk", +"Malgana": "vml", +"MitlatongoMixtec": "vmm", +"Soyaltepec Mazatec": "vmp", +"SoyaltepecMixtec": "vmq", +"Marenje": "vmr", +"Moksela": "vms", +"Muluridyi": "vmu", +"ValleyMaidu": "vmv", +"Makhuwa": "vmw", +"TamazolaMixtec": "vmx", +"AyautlaMazatec": "vmy", +"MazatlánMazatec": "vmz", +"Vano": "vnk", +"Vinmavis": "vnm", +"Vunapu": "vnp", +"Volapük": "vol", +"Voro": "vor", +"Votic": "vot", +"Vera'a": "vra", +"Võro": "vro", +"Varisi": "vrs", +"Burmbar": "vrt", +"MoldovaSign Language": "vsi", +"Venezuelan Sign Language": "vsl", +"ValencianSign Language": "vsv", +"Vitou": "vto", +"Vumbu": "vum", +"Vunjo": "vun", +"Vute": "vut", +"Awa(China)": "vwa", +"Walla Walla": "waa", +"Wab": "wab", +"Wasco-Wishram": "wac", +"Wandamen": "wad", +"Walser": "wae", +"Wakoná": "waf", +"Wa'ema": "wag", +"Watubela": "wah", +"Wares": "wai", +"Waffa": "waj", +"Wolaytta": "wal", +"Wampanoag": "wam", +"Wan": "wan", +"Wappo": "wao", +"Wapishana": "wap", +"Wageman": "waq", +"Waray (Philippines)": "war", +"Washo": "was", +"Kaninuwa": "wat", +"Waurá": "wau", +"Waka": "wav", +"Waiwai": "waw", +"Watam": "wax", +"Wayana": "way", +"Wampur": "waz", +"Warao": "wba", +"Wabo": "wbb", +"Waritai": "wbe", +"Wara": "wbf", +"Wanda": "wbh", +"Vwanji": "wbi", +"Alagwa": "wbj", +"Waigali": "wbk", +"Wakhi": "wbl", +"Wa": "wbm", +"Warlpiri": "wbp", +"Waddar": "wbq", +"Wagdi": "wbr", +"West Bengal Sign Language": "wbs", +"Wanman": "wbt", +"Wajarri": "wbv", +"Woi": "wbw", +"Yanomámi": "wca", +"WaciGbe": "wci", +"Wandji": "wdd", +"Wadaginam": "wdg", +"Wadjiginy": "wdj", +"Wadikali": "wdk", +"Wadjigu": "wdu", +"Wadjabangayi": "wdy", +"Wewaw": "wea", +"Wè Western": "wec", +"Wedau": "wed", +"Wergaia": "weg", +"Weh": "weh", +"Were": "wei", +"WemeGbe": "wem", +"North Wemale": "weo", +"Westphalien": "wep", +"Weri": "wer", +"CameroonPidgin": "wes", +"Perai": "wet", +"Welaung": "weu", +"Wejewa": "wew", +"Yafi": "wfg", +"Wagaya": "wga", +"Wagawaga": "wgb", +"Wangganguru": "wgg", +"Wahgi": "wgi", +"Waigeo": "wgo", +"Wirangu": "wgu", +"Warrgamay": "wgy", +"Manusela": "wha", +"NorthWahgi": "whg", +"Wahau Kenyah": "whk", +"WahauKayan": "whu", +"Southern Toussian": "wib", +"Wichita": "wic", +"Wik-Epa": "wie", +"Wik-Keyangan": "wif", +"Wik-Ngathana": "wig", +"Wik-Me'anha": "wih", +"Minidien": "wii", +"Wik-Iiyanh": "wij", +"Wikalkan": "wik", +"Wilawila": "wil", +"Wik-Mungkan": "wim", +"Ho-Chunk": "win", +"Wiraféd": "wir", +"Wiru": "wiu", +"Muduapa": "wiv", +"Wiyot": "wiy", +"Waja": "wja", +"Warji": "wji", +"Kw'adza": "wka", +"Kumbaran": "wkb", +"Wakde": "wkd", +"Kalanadi": "wkl", +"Keerray-Woorroong": "wkr", +"Kunduvadi": "wku", +"Wakawaka": "wkw", +"Wangkayutyuru": "wky", +"Walio": "wla", +"MwaliComorian": "wlc", +"Wolane": "wle", +"Kunbarlang": "wlg", +"Welaun": "wlh", +"Waioli": "wli", +"Wailaki": "wlk", +"Wali(Sudan)": "wll", +"Middle Welsh": "wlm", +"Walloon": "wln", +"Wolio": "wlo", +"Wailapa": "wlr", +"Wallisian": "wls", +"Wuliwuli": "wlu", +"Wichí Lhamtés Vejoz": "wlv", +"Walak": "wlw", +"Wali (Ghana)": "wlx", +"Waling": "wly", +"Mawa(Nigeria)": "wma", +"Wambaya": "wmb", +"Wamas": "wmc", +"Mamaindé": "wmd", +"Wambule": "wme", +"Western Minyag": "wmg", +"Waima'a": "wmh", +"Wamin": "wmi", +"Maiwa(Indonesia)": "wmm", +"Waamwang": "wmn", +"Wom (Papua New Guinea)": "wmo", +"Wambon": "wms", +"Walmajarri": "wmt", +"Mwani": "wmw", +"Womo": "wmx", +"Wanambre": "wnb", +"Wantoat": "wnc", +"Wandarang": "wnd", +"Waneci": "wne", +"Wanggom": "wng", +"NdzwaniComorian": "wni", +"Wanukaka": "wnk", +"Wanggamala": "wnm", +"Wunumara": "wnn", +"Wano": "wno", +"Wanap": "wnp", +"Usan": "wnu", +"Wintu": "wnw", +"Wanyi": "wny", +"Tyaraity": "woa", +"WèNorthern": "wob", +"Wogeo": "woc", +"Wolani": "wod", +"Woleaian": "woe", +"GambianWolof": "wof", +"Wogamusin": "wog", +"Kamang": "woi", +"Longto": "wok", +"Wolof": "wol", +"Wom(Nigeria)": "wom", +"Wongo": "won", +"Manombai": "woo", +"Woria": "wor", +"Hanga Hundi": "wos", +"Wawonii": "wow", +"Weyto": "woy", +"Maco": "wpc", +"Warluwara": "wrb", +"Warduji": "wrd", +"Warungu": "wrg", +"Wiradhuri": "wrh", +"Wariyangga": "wri", +"Garrwa": "wrk", +"Warlmanpa": "wrl", +"Warumungu": "wrm", +"Warnang": "wrn", +"Worrorra": "wro", +"Waropen": "wrp", +"Wardaman": "wrr", +"Waris": "wrs", +"Waru": "wru", +"Waruna": "wrv", +"Gugu Warra": "wrw", +"Wae Rana": "wrx", +"Merwari": "wry", +"Waray(Australia)": "wrz", +"Warembori": "wsa", +"Adilabad Gondi": "wsg", +"Wusi": "wsi", +"Waskia": "wsk", +"Owenia": "wsr", +"Wasa": "wss", +"Wasu": "wsu", +"Wotapuri-Katarqalai": "wsv", +"Dumpu": "wtf", +"Wathawurrung": "wth", +"Berta": "wti", +"Watakataui": "wtk", +"Mewati": "wtm", +"Wotu": "wtw", +"Wikngenchera": "wua", +"Wunambal": "wub", +"Wudu": "wud", +"Wutunhua": "wuh", +"Silimo": "wul", +"Wumbvu": "wum", +"Bungu": "wun", +"Wurrugu": "wur", +"Wutung": "wut", +"WuChinese": "wuu", +"Wuvulu-Aua": "wuv", +"Wulna": "wux", +"Wauyai": "wuy", +"Waama": "wwa", +"Wakabunga": "wwb", +"Wetamut": "wwo", +"Warrwa": "wwr", +"Wawa": "www", +"Waxianghua": "wxa", +"Wardandi": "wxw", +"Wyandot": "wya", +"Wangaaybuwan-Ngiyambaa": "wyb", +"Woiwurrung": "wyi", +"Wymysorys": "wym", +"Wayoró": "wyr", +"WesternFijian": "wyy", +"Andalusian Arabic": "xaa", +"Sambe": "xab", +"Kachari": "xac", +"Adai": "xad", +"Aequian": "xae", +"Aghwan": "xag", +"Kaimbé": "xai", +"Ararandewára": "xaj", +"Máku": "xak", +"Kalmyk": "xal", +"/Xam": "xam", +"Xamtanga": "xan", +"Khao": "xao", +"Apalachee": "xap", +"Aquitanian": "xaq", +"Karami": "xar", +"Kamas": "xas", +"Katawixi": "xat", +"Kauwera": "xau", +"Xavánte": "xav", +"Kawaiisu": "xaw", +"Kayan Mahakam": "xay", +"LowerBurdekin": "xbb", +"Bactrian": "xbc", +"Bindal": "xbd", +"Bigambal": "xbe", +"Bunganditj": "xbg", +"Kombio": "xbi", +"Birrpayi": "xbj", +"Middle Breton": "xbm", +"Kenaboi": "xbn", +"Bolgarian": "xbo", +"Bibbulman": "xbp", +"Kambera": "xbr", +"Kambiwá": "xbw", +"Batjala": "xby", +"Cumbric": "xcb", +"Camunic": "xcc", +"Celtiberian": "xce", +"Cisalpine Gaulish": "xcg", +"Chemakum": "xch", +"ClassicalArmenian": "xcl", +"Comecrudo": "xcm", +"Cotoname": "xcn", +"Chorasmian": "xco", +"Carian": "xcr", +"Classical Tibetan": "xct", +"Curonian": "xcu", +"Chuvantsy": "xcv", +"Coahuilteco": "xcw", +"Cayuse": "xcy", +"Darkinyung": "xda", +"Dacian": "xdc", +"Dharuk": "xdk", +"Edomite": "xdm", +"Kwandu": "xdo", +"Malayic Dayak": "xdy", +"Eblan": "xeb", +"Hdi": "xed", +"//Xegwi": "xeg", +"Kelo": "xel", +"Kembayan": "xem", +"Epi-Olmec": "xep", +"Xerénte": "xer", +"Kesawai": "xes", +"Xetá": "xet", +"Keoru-Ahia": "xeu", +"Faliscan": "xfa", +"Galatian": "xga", +"Gbin": "xgb", +"Gudang": "xgd", +"Gabrielino-Fernandeño": "xgf", +"Goreng": "xgg", +"Garingbal": "xgi", +"Galindan": "xgl", +"Dharumbal": "xgm", +"Garza": "xgr", +"Unggumi": "xgu", +"Guwa": "xgw", +"Harami": "xha", +"Hunnic": "xhc", +"Hadrami": "xhd", +"Khetrani": "xhe", +"Xhosa": "xho", +"Hernican": "xhr", +"Hattic": "xht", +"Hurrian": "xhu", +"Khua": "xhv", +"Iberian": "xib", +"Xiri": "xii", +"Illyrian": "xil", +"Xinca": "xin", +"Xiriâna": "xir", +"Kisan": "xis", +"IndusValley Language": "xiv", +"Xipaya": "xiy", +"Minjungbal": "xjb", +"Jaitmatang": "xjt", +"Kalkoti": "xka", +"Northern Nago": "xkb", +"Kho'ini": "xkc", +"Mendalam Kayan": "xkd", +"Kereho": "xke", +"Khengkha": "xkf", +"Kagoro": "xkg", +"Kenyan Sign Language": "xki", +"Kajali": "xkj", +"Kaco'": "xkk", +"MainstreamKenyah": "xkl", +"KayanRiver Kayan": "xkn", +"Kiorr": "xko", +"Kabatei": "xkp", +"Koroni": "xkq", +"Xakriabá": "xkr", +"Kumbewaha": "xks", +"Kantosi": "xkt", +"Kaamba": "xku", +"Kgalagadi": "xkv", +"Kembra": "xkw", +"Karore": "xkx", +"Uma'Lasan": "xky", +"Kurtokha": "xkz", +"Kamula": "xla", +"Loup B": "xlb", +"Lycian": "xlc", +"Lydian": "xld", +"Lemnian": "xle", +"Ligurian(Ancient)": "xlg", +"Liburnian": "xli", +"Alanic": "xln", +"LoupA": "xlo", +"Lepontic": "xlp", +"Lusitanian": "xls", +"Cuneiform Luwian": "xlu", +"Elymian": "xly", +"Mushungulu": "xma", +"Mbonga": "xmb", +"Makhuwa-Marrevone": "xmc", +"Mbedam": "xmd", +"Median": "xme", +"Mingrelian": "xmf", +"Mengaka": "xmg", +"Kuku-Muminh": "xmh", +"Majera": "xmj", +"AncientMacedonian": "xmk", +"Malaysian Sign Language": "xml", +"ManadoMalay": "xmm", +"ManichaeanMiddle Persian": "xmn", +"Morerebi": "xmo", +"Kuku-Mu'inh": "xmp", +"Kuku-Mangk": "xmq", +"Meroitic": "xmr", +"Moroccan Sign Language": "xms", +"Matbat": "xmt", +"Kamu": "xmu", +"AntankaranaMalagasy": "xmv", +"TsimihetyMalagasy": "xmw", +"Maden": "xmx", +"Mayaguduna": "xmy", +"MoriBawah": "xmz", +"Ancient North Arabian": "xna", +"Kanakanabu": "xnb", +"Middle Mongolian": "xng", +"Kuanhua": "xnh", +"Ngarigu": "xni", +"Ngoni (Tanzania)": "xnj", +"Nganakarti": "xnk", +"Ngumbarl": "xnm", +"NorthernKankanay": "xnn", +"Anglo-Norman": "xno", +"Ngoni (Mozambique)": "xnq", +"Kangri": "xnr", +"Kanashi": "xns", +"Narragansett": "xnt", +"Nukunul": "xnu", +"Nyiyaparli": "xny", +"Kenzi": "xnz", +"O'chi'chi'": "xoc", +"Kokoda": "xod", +"Soga": "xog", +"Kominimung": "xoi", +"Xokleng": "xok", +"Komo (Sudan)": "xom", +"Konkomba": "xon", +"Xukurú": "xoo", +"Kopar": "xop", +"Korubo": "xor", +"Kowaki": "xow", +"Pirriya": "xpa", +"Northeastern Tasmanian": "xpb", +"Pecheneg": "xpc", +"Oyster Bay Tasmanian": "xpd", +"LiberiaKpelle": "xpe", +"Southeast Tasmanian": "xpf", +"Phrygian": "xpg", +"North Midlands Tasmanian": "xph", +"Pictish": "xpi", +"Mpalitjanh": "xpj", +"KulinaPano": "xpk", +"Port Sorell Tasmanian": "xpl", +"Pumpokol": "xpm", +"Kapinawá": "xpn", +"Pochutec": "xpo", +"Puyo-Paekche": "xpp", +"Mohegan-Pequot": "xpq", +"Parthian": "xpr", +"Pisidian": "xps", +"Punthamara": "xpt", +"Punic": "xpu", +"Northern Tasmanian": "xpv", +"Northwestern Tasmanian": "xpw", +"Southwestern Tasmanian": "xpx", +"Puyo": "xpy", +"Bruny Island Tasmanian": "xpz", +"Karakhanid": "xqa", +"Qatabanian": "xqt", +"Krahô": "xra", +"EasternKaraboro": "xrb", +"Gundungurra": "xrd", +"Kreye": "xre", +"Minang": "xrg", +"Krikati-Timbira": "xri", +"Armazic": "xrm", +"Arin": "xrn", +"Raetic": "xrr", +"Aranama-Tamique": "xrt", +"Marriammu": "xru", +"Karawa": "xrw", +"Sabaean": "xsa", +"Tinà Sambal": "xsb", +"Scythian": "xsc", +"Sidetic": "xsd", +"Sempan": "xse", +"Shamang": "xsh", +"Sio": "xsi", +"Subi": "xsj", +"South Slavey": "xsl", +"Kasem": "xsm", +"Sanga(Nigeria)": "xsn", +"Solano": "xso", +"Silopi": "xsp", +"Makhuwa-Saka": "xsq", +"Sherpa": "xsr", +"Assan": "xss", +"Sanumá": "xsu", +"Sudovian": "xsv", +"Saisiyat": "xsy", +"AlcozaucaMixtec": "xta", +"ChazumbaMixtec": "xtb", +"Katcha-Kadugli-Miri": "xtc", +"Diuxi-Tilantongo Mixtec": "xtd", +"Ketengban": "xte", +"TransalpineGaulish": "xtg", +"Yitha Yitha": "xth", +"SinicahuaMixtec": "xti", +"SanJuan Teita Mixtec": "xtj", +"Tijaltepec Mixtec": "xtl", +"MagdalenaPeñasco Mixtec": "xtm", +"Northern Tlaxiaco Mixtec": "xtn", +"Tokharian A": "xto", +"SanMiguel Piedras Mixtec": "xtp", +"Tumshuqese": "xtq", +"EarlyTripuri": "xtr", +"Sindihui Mixtec": "xts", +"TacahuaMixtec": "xtt", +"CuyamecalcoMixtec": "xtu", +"Thawa": "xtv", +"Tawandê": "xtw", +"YoloxochitlMixtec": "xty", +"AluKurumba": "xua", +"BettaKurumba": "xub", +"Umiida": "xud", +"Kunigami": "xug", +"JennuKurumba": "xuj", +"Ngunawal": "xul", +"Umbrian": "xum", +"Unggaranggu": "xun", +"Kuo": "xuo", +"UpperUmpqua": "xup", +"Urartian": "xur", +"Kuthant": "xut", +"Kxoe": "xuu", +"Venetic": "xve", +"Kamviri": "xvi", +"Vandalic": "xvn", +"Volscian": "xvo", +"Vestinian": "xvs", +"Kwaza": "xwa", +"Woccon": "xwc", +"Wadi Wadi": "xwd", +"Xwela Gbe": "xwe", +"Kwegu": "xwg", +"Wajuk": "xwj", +"Wangkumara": "xwk", +"Western Xwla Gbe": "xwl", +"WrittenOirat": "xwo", +"KwerbaMamberamo": "xwr", +"Wotjobaluk": "xwt", +"Wemba Wemba": "xww", +"Boro (Ghana)": "xxb", +"Ke'o": "xxk", +"Minkin": "xxm", +"Koropó": "xxr", +"Tambora": "xxt", +"Yaygir": "xya", +"Yandjibara": "xyb", +"Mayi-Yapi": "xyj", +"Mayi-Kulan": "xyk", +"Yalakalore": "xyl", +"Mayi-Thakurti": "xyt", +"Yorta Yorta": "xyy", +"Zhang-Zhung": "xzh", +"Zemgalian": "xzm", +"AncientZapotec": "xzp", +"Yaminahua": "yaa", +"Yuhup": "yab", +"PassValley Yali": "yac", +"Yagua": "yad", +"Pumé": "yae", +"Yaka(Democratic Republic of Congo)": "yaf", +"Yámana": "yag", +"Yazgulyam": "yah", +"Yagnobi": "yai", +"Banda-Yangere": "yaj", +"Yakama": "yak", +"Yalunka": "yal", +"Yamba": "yam", +"Mayangna": "yan", +"Yao": "yao", +"Yapese": "yap", +"Yaqui": "yaq", +"Yabarana": "yar", +"Nugunu(Cameroon)": "yas", +"Yambeta": "yat", +"Yuwana": "yau", +"Yangben": "yav", +"Yawalapití": "yaw", +"Yauma": "yax", +"Agwagwune": "yay", +"Lokaa": "yaz", +"Yala": "yba", +"Yemba": "ybb", +"WestYugur": "ybe", +"Yakha": "ybh", +"Yamphu": "ybi", +"Hasha": "ybj", +"Bokha": "ybk", +"Yukuben": "ybl", +"Yaben": "ybm", +"Yabaâna": "ybn", +"Yabong": "ybo", +"Yawiyo": "ybx", +"Yaweyuha": "yby", +"Chesu": "ych", +"Lolopo": "ycl", +"Yucuna": "ycn", +"Chepya": "ycp", +"Yanda": "yda", +"Eastern Yiddish": "ydd", +"YangumDey": "yde", +"Yidgha": "ydg", +"Yoidik": "ydk", +"Ravula": "yea", +"Yeniche": "yec", +"Yimas": "yee", +"Yeni": "yei", +"Yevanic": "yej", +"Yela": "yel", +"Tarok": "yer", +"Yeskwa": "yes", +"Yetfa": "yet", +"Yerukula": "yeu", +"Yapunda": "yev", +"Yeyi": "yey", +"Malyangapa": "yga", +"Yiningayi": "ygi", +"Yangum Gel": "ygl", +"Yagomi": "ygm", +"Gepo": "ygp", +"Yagaria": "ygr", +"Yolŋu Sign Language": "ygs", +"Yugul": "ygu", +"Yagwoia": "ygw", +"BahaBuyang": "yha", +"Judeo-Iraqi Arabic": "yhd", +"Hlepho Phowa": "yhl", +"Yan-nhaŋu Sign Language": "yhs", +"Yinggarda": "yia", +"Yiddish": "yid", +"Ache": "yif", +"WusaNasu": "yig", +"WesternYiddish": "yih", +"Yidiny": "yii", +"Yindjibarndi": "yij", +"DongshanbaLalo": "yik", +"Yindjilandji": "yil", +"YimchungruNaga": "yim", +"Yinchia": "yin", +"Pholo": "yip", +"Miqie": "yiq", +"NorthAwyu": "yir", +"Yis": "yis", +"EasternLalu": "yit", +"Awu": "yiu", +"NorthernNisu": "yiv", +"AxiYi": "yix", +"Azhe": "yiz", +"Yakan": "yka", +"NorthernYukaghir": "ykg", +"Yoke": "yki", +"Yakaikeke": "ykk", +"Khlula": "ykl", +"Kap": "ykm", +"Kua-nsi": "ykn", +"Yasa": "yko", +"Yekora": "ykr", +"Kathu": "ykt", +"Kuamasi": "yku", +"Yakoma": "yky", +"Yaul": "yla", +"Yaleba": "ylb", +"Yele": "yle", +"Yelogu": "ylg", +"AnggurukYali": "yli", +"Yil": "yll", +"Limi": "ylm", +"LangnianBuyang": "yln", +"NaluoYi": "ylo", +"Yalarnnga": "ylr", +"Aribwaung": "ylu", +"Nyâlayu": "yly", +"Yambes": "ymb", +"Southern Muji": "ymc", +"Muda": "ymd", +"Yameo": "yme", +"Yamongeri": "ymg", +"Mili": "ymh", +"Moji": "ymi", +"Makwe": "ymk", +"Iamalele": "yml", +"Maay": "ymm", +"Yamna": "ymn", +"YangumMon": "ymo", +"Yamap": "ymp", +"QilaMuji": "ymq", +"Malasar": "ymr", +"Mysian": "yms", +"NorthernMuji": "ymx", +"Muzi": "ymz", +"Aluo": "yna", +"Yandruwandha": "ynd", +"Lang'e": "yne", +"Yango": "yng", +"NaukanYupik": "ynk", +"Yangulam": "ynl", +"Yana": "ynn", +"Yong": "yno", +"Yendang": "ynq", +"Yansi": "yns", +"Yahuna": "ynu", +"Yoba": "yob", +"Yogad": "yog", +"Yonaguni": "yoi", +"Yokuts": "yok", +"Yola": "yol", +"Yombe": "yom", +"Yonggom": "yon", +"Yoruba": "yor", +"Yotti": "yot", +"Yoron": "yox", +"Yoy": "yoy", +"Phala": "ypa", +"LaboPhowa": "ypb", +"Phola": "ypg", +"Phupha": "yph", +"Phuma": "ypm", +"AniPhowa": "ypn", +"AloPhola": "ypo", +"Phupa": "ypp", +"Phuza": "ypz", +"Yerakai": "yra", +"Yareba": "yrb", +"Yaouré": "yre", +"Nenets": "yrk", +"Nhengatu": "yrl", +"Yirrk-Mel": "yrm", +"Yerong": "yrn", +"Yaroamë": "yro", +"Yarsun": "yrs", +"Yarawata": "yrw", +"Yarluyandi": "yry", +"Yassic": "ysc", +"Samatao": "ysd", +"Sonaga": "ysg", +"YugoslavianSign Language": "ysl", +"Myanmar Sign Language": "ysm", +"Sani": "ysn", +"Nisi(China)": "yso", +"SouthernLolopo": "ysp", +"Sirenik Yupik": "ysr", +"Yessan-Mayo": "yss", +"Sanie": "ysy", +"Talu": "yta", +"Tanglang": "ytl", +"Thopho": "ytp", +"YoutWam": "ytw", +"Yatay": "yty", +"Yucateco": "yua", +"Yugambal": "yub", +"Yuchi": "yuc", +"Judeo-Tripolitanian Arabic": "yud", +"YueChinese": "yue", +"Havasupai-Walapai-Yavapai": "yuf", +"Yug": "yug", +"Yurutí": "yui", +"Karkar-Yuri": "yuj", +"Yuki": "yuk", +"Yulu": "yul", +"Quechan": "yum", +"Bena(Nigeria)": "yun", +"Yukpa": "yup", +"Yuqui": "yuq", +"Yurok": "yur", +"Yopno": "yut", +"Yau(Morobe Province)": "yuw", +"Southern Yukaghir": "yux", +"East Yugur": "yuy", +"Yuracare": "yuz", +"Yawa": "yva", +"Yavitero": "yvt", +"Kalou": "ywa", +"Yinhawangka": "ywg", +"Western Lalu": "ywl", +"Yawanawa": "ywn", +"Wuding-Luquan Yi": "ywq", +"Yawuru": "ywr", +"XishanbaLalo": "ywt", +"WumengNasu": "ywu", +"Yawarawarga": "yww", +"Mayawali": "yxa", +"Yagara": "yxg", +"Yardliyawarra": "yxl", +"Yinwum": "yxm", +"Yuyu": "yxu", +"Yabula Yabula": "yxy", +"Yir Yoront": "yyr", +"Yau (Sandaun Province)": "yyu", +"Ayizi": "yyz", +"E'maBuyang": "yzg", +"Zokhuo": "yzk", +"Sierrade Juárez Zapotec": "zaa", +"San Juan Guelavía Zapotec": "zab", +"Ocotlán Zapotec": "zac", +"Cajonos Zapotec": "zad", +"YareniZapotec": "zae", +"AyoquescoZapotec": "zaf", +"Zaghawa": "zag", +"Zangwal": "zah", +"Isthmus Zapotec": "zai", +"Zaramo": "zaj", +"Zanaki": "zak", +"Zauzou": "zal", +"Miahuatlán Zapotec": "zam", +"OzolotepecZapotec": "zao", +"Zapotec": "zap", +"AloápamZapotec": "zaq", +"RincónZapotec": "zar", +"Santo Domingo Albarradas Zapotec": "zas", +"Tabaa Zapotec": "zat", +"Zangskari": "zau", +"Yatzachi Zapotec": "zav", +"Mitla Zapotec": "zaw", +"XadaniZapotec": "zax", +"Zayse-Zergulla": "zay", +"Zari": "zaz", +"Balaibalan": "zba", +"CentralBerawan": "zbc", +"East Berawan": "zbe", +"Blissymbols": "zbl", +"Batui": "zbt", +"Bu (Bauchi State)": "zbu", +"WestBerawan": "zbw", +"Coatecas Altas Zapotec": "zca", +"CentralHongshuihe Zhuang": "zch", +"Ngazidja Comorian": "zdj", +"Zeeuws": "zea", +"Zenag": "zeg", +"Eastern Hongshuihe Zhuang": "zeh", +"Zenaga": "zen", +"Kinga": "zga", +"Guibei Zhuang": "zgb", +"Standard Moroccan Tamazight": "zgh", +"MinzZhuang": "zgm", +"GuibianZhuang": "zgn", +"Magori": "zgr", +"Zhuang": "zha", +"Zhaba": "zhb", +"Dai Zhuang": "zhd", +"Zhire": "zhi", +"NongZhuang": "zhn", +"Chinese": "zho", +"Zhoa": "zhw", +"Zia": "zia", +"Zimbabwe Sign Language": "zib", +"Zimakani": "zik", +"Zialo": "zil", +"Mesme": "zim", +"Zinza": "zin", +"Zigula": "ziw", +"Zizilivakan": "ziz", +"Kaimbulawa": "zka", +"Koibal": "zkb", +"Kadu": "zkd", +"Koguryo": "zkg", +"Khorezmian": "zkh", +"Karankawa": "zkk", +"Kanan": "zkn", +"Kott": "zko", +"São Paulo Kaingáng": "zkp", +"Zakhring": "zkr", +"Kitan": "zkt", +"Kaurna": "zku", +"Krevinian": "zkv", +"Khazar": "zkz", +"Zula": "zla", +"LiujiangZhuang": "zlj", +"Malay(individual language)": "zlm", +"Lianshan Zhuang": "zln", +"LiuqianZhuang": "zlq", +"Manda (Australia)": "zma", +"Zimba": "zmb", +"Margany": "zmc", +"Maridan": "zmd", +"Mangerr": "zme", +"Mfinu": "zmf", +"Marti Ke": "zmg", +"Makolkol": "zmh", +"Negeri Sembilan Malay": "zmi", +"Maridjabin": "zmj", +"Mandandanyi": "zmk", +"Madngele": "zml", +"Marimanindji": "zmm", +"Mbangwe": "zmn", +"Molo": "zmo", +"Mpuono": "zmp", +"Mituku": "zmq", +"Maranunggu": "zmr", +"Mbesa": "zms", +"Maringarr": "zmt", +"Muruwari": "zmu", +"Mbariman-Gudhinma": "zmv", +"Mbo (Democratic Republic of Congo)": "zmw", +"Bomitaba": "zmx", +"Mariyedi": "zmy", +"Mbandja": "zmz", +"Zan Gula": "zna", +"Zande(individual language)": "zne", +"Mang": "zng", +"Manangkari": "znk", +"Mangas": "zns", +"CopainaláZoque": "zoc", +"ChimalapaZoque": "zoh", +"Zou": "zom", +"AsunciónMixtepec Zapotec": "zoo", +"TabascoZoque": "zoq", +"Rayón Zoque": "zor", +"FranciscoLeón Zoque": "zos", +"Lachiguiri Zapotec": "zpa", +"Yautepec Zapotec": "zpb", +"ChoapanZapotec": "zpc", +"Southeastern Ixtlán Zapotec": "zpd", +"Petapa Zapotec": "zpe", +"SanPedro Quiatoni Zapotec": "zpf", +"Guevea De Humboldt Zapotec": "zpg", +"TotomachapanZapotec": "zph", +"Santa María Quiegolani Zapotec": "zpi", +"Quiavicuzas Zapotec": "zpj", +"Tlacolulita Zapotec": "zpk", +"LachixíoZapotec": "zpl", +"MixtepecZapotec": "zpm", +"Santa Inés Yatzechi Zapotec": "zpn", +"AmatlánZapotec": "zpo", +"ElAlto Zapotec": "zpp", +"Zoogocho Zapotec": "zpq", +"SantiagoXanica Zapotec": "zpr", +"CoatlánZapotec": "zps", +"SanVicente Coatlán Zapotec": "zpt", +"YalálagZapotec": "zpu", +"Chichicapan Zapotec": "zpv", +"ZanizaZapotec": "zpw", +"San Baltazar Loxicha Zapotec": "zpx", +"MazaltepecZapotec": "zpy", +"Texmelucan Zapotec": "zpz", +"QiubeiZhuang": "zqe", +"Kara(Korea)": "zra", +"Mirgan": "zrg", +"Zerenkel": "zrn", +"Záparo": "zro", +"Zarphatic": "zrp", +"Mairasi": "zrs", +"Sarasira": "zsa", +"Kaskean": "zsk", +"ZambianSign Language": "zsl", +"Standard Malay": "zsm", +"Southern Rincon Zapotec": "zsr", +"Sukurum": "zsu", +"ElotepecZapotec": "zte", +"XanaguíaZapotec": "ztg", +"Lapaguía-Guivini Zapotec": "ztl", +"San Agustín Mixtepec Zapotec": "ztm", +"Santa Catarina Albarradas Zapotec": "ztn", +"Loxicha Zapotec": "ztp", +"Quioquitani-QuieríZapotec": "ztq", +"TilquiapanZapotec": "zts", +"TejalapanZapotec": "ztt", +"GüiláZapotec": "ztu", +"Zaachila Zapotec": "ztx", +"YateeZapotec": "zty", +"Zeem": "zua", +"Tokano": "zuh", +"Zulu": "zul", +"Kumzari": "zum", +"Zuni": "zun", +"Zumaya": "zuy", +"Zay": "zwa", +"Nolinguistic content": "zxx", +"Yongbei Zhuang": "zyb", +"YangZhuang": "zyg", +"YoujiangZhuang": "zyj", +"YongnanZhuang": "zyn", +"Zyphe": "zyp", +"Zaza": "zza", +"ZuojiangZhuang": "zzj" +} diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.09.30__Add_share_token_to_workspaceitem.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.09.30__Add_share_token_to_workspaceitem.sql new file mode 100644 index 000000000000..af472c74f97b --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2024.09.30__Add_share_token_to_workspaceitem.sql @@ -0,0 +1,9 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +ALTER TABLE workspaceitem ADD share_token varchar(32); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.09.30__Add_share_token_to_workspaceitem.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.09.30__Add_share_token_to_workspaceitem.sql new file mode 100644 index 000000000000..af472c74f97b --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.09.30__Add_share_token_to_workspaceitem.sql @@ -0,0 +1,9 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +ALTER TABLE workspaceitem ADD share_token varchar(32); diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.10.25__insert_default_licenses.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.10.25__insert_default_licenses.sql new file mode 100644 index 000000000000..301cdecf88af --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2024.10.25__insert_default_licenses.sql @@ -0,0 +1,202 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +-- +-- Default License Definitions with Associated License Labels and Mappings +-- NOTE: Do NOT use this file if your repository already contains licenses. +-- + +-- +-- Data for Name: license_definition; Type: TABLE DATA; Schema: public; Owner: dspace +-- + +-- Insert data into tables only if the tables (license_definition, license_label, license_label_extended_mapping) +-- are empty +DO $$ +BEGIN + -- Check if the 'license_definition' table is empty + PERFORM 1 FROM public.license_definition LIMIT 1; + IF NOT FOUND THEN + -- Check if the 'license_label' table is empty + PERFORM 1 FROM public.license_label LIMIT 1; + IF NOT FOUND THEN + -- Check if the 'license_label_extended_mapping' table is empty + PERFORM 1 FROM public.license_label_extended_mapping LIMIT 1; + IF NOT FOUND THEN + -- All three tables are empty, so insert data + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (1, 'GNU General Public Licence, version 3', 'http://opensource.org/licenses/GPL-3.0', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (2, 'GNU General Public License, version 2', 'http://www.gnu.org/licenses/gpl-2.0.html', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (3, 'The MIT License (MIT)', 'http://opensource.org/licenses/mit-license.php', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (4, 'Artistic License 2.0', 'http://opensource.org/licenses/Artistic-2.0', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (5, 'Artistic License (Perl) 1.0', 'http://opensource.org/licenses/Artistic-Perl-1.0', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (6, 'Attribution-NonCommercial-NoDerivs 3.0 Unported (CC BY-NC-ND 3.0)', 'http://creativecommons.org/licenses/by-nc-nd/3.0/', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (7, 'BSD 2-Clause "Simplified" or "FreeBSD" license', 'http://opensource.org/licenses/BSD-2-Clause', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (8, 'BSD 3-Clause "New" or "Revised" license', 'http://opensource.org/licenses/BSD-3-Clause', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (9, 'Attribution-NonCommercial 3.0 Unported (CC BY-NC 3.0)', 'http://creativecommons.org/licenses/by-nc/3.0/', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (10, 'Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)', 'http://creativecommons.org/licenses/by-nc-sa/3.0/', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (11, 'Attribution-NoDerivs 3.0 Unported (CC BY-ND 3.0)', 'http://creativecommons.org/licenses/by-nd/3.0/', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (12, 'Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)', 'http://creativecommons.org/licenses/by-sa/3.0/', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (13, 'Creative Commons - Attribution 3.0 Unported (CC BY 3.0)', 'http://creativecommons.org/licenses/by/3.0/', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (14, 'Public Domain Dedication (CC Zero)', 'http://creativecommons.org/publicdomain/zero/1.0/', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (22, 'Apache License 2.0', 'http://opensource.org/licenses/Apache-2.0', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (24, 'Affero General Public License 1 (AGPL-1.0)', 'http://www.affero.org/oagpl.html', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (25, 'Affero General Public License 3 (AGPL-3.0)', 'http://opensource.org/licenses/AGPL-3.0', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (26, 'Common Development and Distribution License (CDDL-1.0)', 'http://opensource.org/licenses/CDDL-1.0', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (27, 'Eclipse Public License 1.0 (EPL-1.0)', 'http://opensource.org/licenses/EPL-1.0', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (28, 'GNU General Public License 2 or later (GPL-2.0)', 'http://opensource.org/licenses/GPL-2.0', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (29, 'GNU Library or "Lesser" General Public License 2.1 (LGPL-2.1)', 'http://opensource.org/licenses/LGPL-2.1', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (30, 'GNU Library or "Lesser" General Public License 2.1 or later (LGPL-2.1)', 'http://opensource.org/licenses/LGPL-2.1', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (31, 'GNU Library or "Lesser" General Public License 3.0 (LGPL-3.0)', 'http://opensource.org/licenses/LGPL-3.0', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (32, 'Mozilla Public License 2.0', 'http://opensource.org/licenses/MPL-2.0', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (33, 'Open Data Commons Attribution License (ODC-By)', 'http://opendatacommons.org/licenses/by/summary/', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (34, 'Open Data Commons Open Database License (ODbL)', 'http://opendatacommons.org/licenses/odbl/summary/', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (35, 'Open Data Commons Public Domain Dedication and License (PDDL)', 'http://opendatacommons.org/licenses/pddl/summary/', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (36, 'Public Domain Mark (PD)', 'http://creativecommons.org/publicdomain/mark/1.0/', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (37, 'Creative Commons - Attribution 4.0 International (CC BY 4.0)', 'http://creativecommons.org/licenses/by/4.0/', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (38, 'Creative Commons - Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)', 'http://creativecommons.org/licenses/by-sa/4.0/', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (39, 'Creative Commons - Attribution-NoDerivatives 4.0 International (CC BY-ND 4.0)', 'http://creativecommons.org/licenses/by-nd/4.0/', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (40, 'Creative Commons - Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)', 'http://creativecommons.org/licenses/by-nc/4.0/', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (41, 'Creative Commons - Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)', 'http://creativecommons.org/licenses/by-nc-sa/4.0/', NULL, NULL, NULL, 0, NULL); + INSERT INTO public.license_definition (license_id, name, definition, user_registration_id, label_id, created_on, confirmation, required_info) VALUES (42, 'Creative Commons - Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)', 'http://creativecommons.org/licenses/by-nc-nd/4.0/', NULL, NULL, NULL, 0, NULL); + + -- + -- Data for Name: license_label; Type: TABLE DATA; Schema: public; Owner: dspace + -- + + INSERT INTO public.license_label (label_id, label, title, icon, is_extended) VALUES (20, 'PUB', 'Publicly Available', NULL, false); + INSERT INTO public.license_label (label_id, label, title, icon, is_extended) VALUES (21, 'ACA', 'Academic Use', '\x89504e470d0a1a0a0000000d494844520000003500000016080600000060ded4e8000000097048597300000b1300000b1301009a9c1800000e256943435050686f746f73686f70204943432070726f66696c65000078dad5976750135c97c74f0a49e848ef448a204d7a514120201d218054a524a199841002084a13055190a28822a0f4a248095d407a551101b1a0d81ebb8f05441031fbc17df77d6676f6c3ceec97fd7dfacf9973e79c39e7cedcfb0710ec0c6230a84821001a9dc524da5ae1bd7d7cf1d847c00302200e1ab02d8814cdb074757582ff91d5fb80000098d50a6230a8f0bf43804c89260120340120921c4da201208a01c09ac460b20010cf00e06e1c8bc1024036018028d3dbc71700390c00a2a17ff40300100dfea33f028028d383480040010008c4924259002821008c309d1c4e07e05606c09893c282c800826400d0a4d122c90082b900a016fc8fb3a1ffd07ffa040000314224359289772258e30941d4f06066108b4286ff6368d4987fd54300001f85eee90e006a00200b0488042a440213f0e00404b0063c102008a8100ec1c08420600105c82cca61160000219211cf0c0f0d63e12d190c2a45136f4f27696be2f5747475e0ff13de3ebef83f6a851f10008008fff5ef987f3c80411800fac8bf630c5e806b5f00a4ff91a7ee06205f0bc06e27c53063ff3563c46d803f77e53f412100900020053ed0899045e4226590eda840b402fa19572fa6015b822be6aee7b9c9fb806f4560aba0a7d0f12d23c23f444dc458e26d12eb5286d20c9956d955791d85087cf5d6792511650b95a06df9aa5d6a6fd44103af49d0f2d60edf71442747b742ef8a7eb941a361b35191f179933cd3c49de45d4ebbf5cca4ccc1fce59edb167d96b556058434eb4336ae7b0d6de5ecc0ee8dfdbc43a7e379a72467928bf53e255784eb73b71e62917b9c879be7764fcefe59af1aef241f575f65df6f7ec3fe670e781d5439f877407be0b1208760c1e07ba47cb23b4594f220e47ca8679848d854f8c9089b43e8437d54164d8bf6825e11e9cb10620c47c5330da221fa05eb564c53ecb938c66162bc5e825cc2fa914747d989f949b1c9c4148d545ceaeb63fd69178e479e704c974fff9171e7645126f594f96981d34fb2dab253cf38e588e72ce5d6e431f3cdcf22cf8e9dcb29f0382f797eb1b0e4c2818bf2179f16155ef22b162fbe579253ea7299f7f2f8958c3242d9eff2a60a5aa56ae55f55c5d50ed59f6a926b856acbeaf4eafaea9dea97ae1ebaba762dbd41b8a1ecfa8eebbd8d4e8d8f9b429a569bd35b445baeb0b5d983adfb5a9fb645b5437b4e8742474b27a173be2ba28bd37df686ea8dde1e9f9e95de937dca7d8337836ffeeebf326033f07e306768cfd0dbe1c211a751d4e8cdb1b471e284f2247272796aedb6e49da8e9040010001d2041157c47b820d8485d642fca1bcd831ee72ac4c46129b8406e12cf11de5cbeebfc0f049142c65b42850b44a6c470e2161234c9cb5213d2ebb29a72eef2690a2df8fb5b7f2a892a6ba9386fa3aa26aae56caf52efd118d27ca2f546fbf98e4f3a3f75d7f556f4970dbe1bae1abd327e6572df747467c7aedadd4566d9e647f7845bb85a1a58491250844fd673361d7b2fd9a6d985da5b3b6c73e473fce034ea5ceb92becfdfd5c84dc8ed3d71dcbdd423dad369bffcfe2f5e23de053e145f633facdfacff9503f483bb02b8031e06d6063182f790b849b3e4524a68884ec846e870d8e970cf08c5885787d8d42334029d877e37f202831c65c294894647bf654dc4d4c69e8ca31df688374a9048583e327fb42fb134e94872400a2155e518fad8cbb4a9e3d527b2d21919ee270d33c533574fdd3fdd91559a9d7ce6608e592e3e0f97f7317ffa6ccbb97305acf33e859617142f222ffe553479a9a5b8b824a1d4ffb2c315d532c1b2f5f2c58abecacaaaeceaf81a4aad439d563db6fef6d5d3d7ec1bd00d5dd7631ab51bdf375535fbb708b64cb2d35a4d5a3fb655b47b77f0740c74c674a9773dedceba6171e3474f632fa54fb26ff6667abf59ffca40fd60e090ccd083e1f323c451fed1a9b133e36e1312137393c553945b5ab7566f0fddc99b0ebe6b3cc333f3f45eebec99b943f3f6f79517300bef1ecc3cbcf048efbff65f03eb0877441752035987d2438da0c95cfc5cc39864ac258e07f79cbb87a78837858fce4f16f017dc2fe4b98528ec2d724034482c423c5e2243324faa44ba45664076546e41febdc257fc9a2242895719a722bc4d50954f4d7a3bb73a5a03a1b1acf9516b517b6247bb4e896e8a1e459f60a06d88337c61d46f5c6672dc94bcd36a97ccaeefbbe7cddacdcfeda15bd8596a5821ac16093dd6c536717b5d6cb5ec78ed5edbf73b5c746438ed7596755e7599da77c5f5b09b035199b8ea3ee151ea19b5dfca4bd8eb9537db27c5d7dd4fceef8d7feb81d4836e010a016f039b82e2836d4882a487e40a0a354437e47b68575872b84b8448c4c2a1126a384d9df685de1999c4708a128e7ac4ac8aa6b3cc63b863e662cbe3e8874de2d1f1330925476847cd12f9131793ae2627a410531553578e8da7151f8f3d619b2e9bbe9271eb647966fc29e269f52c64d6a36cf699829cc85cbb3c7cde7afefdb39de70a0a98e71d0a552fa02f2c5dec2d2abd1453ec5562582a7a19ae20cbf8ca852b442b25aa64aa156a146a65ea24ebc5ae8a5e136910bd2ed628d924d32cd322c696685568536bd7ead0ea34e8b2ea76bb11d413dd9bde577273ac7f6960634879d8612461b479ecdd84f264e054f9adb53beed34d3342f7e26697e73317441eb01f452dea3fd95cda78817e65fbfaeabb9d1f777ece5eee5c6fe47000fefc1900003046001702007ce400dc6c01b276036c1b0490e00170e507f03005a4b62820be0f02c22ae71fefc74ef085a3500203f0128146a8219c100cc4394417620989416a233d9189c82ae45de43a4a09e5864a44d5a31ea27168137418ba087d870bc565c245e3aae07a8c11c3b8624e63c6b038ac2df604760cc78fdb8f2bc2bde4d6e266710ff388f19079d8bc58de00de4e3e613e1adf38bf0aff69fe65012f816e4115c112213ea123425fb6d0b77c12a6097f116189ac8ba689f189e58a4b88574a684b744b3a4a2e49c54af34857ca58ca3c9465c889c875c9fbc8ff52a8c4efc3ffde5aaf7840495869443951c544e5c7b62ed564b5bddb79b73f54afd488d774d052d3e6d25eda31a253ab7b4a2f4e9f62e06fe86eb4cfd8c664afa9fd4e975d81bbc3cde2cc8fed29b3e8b25cb4fadb5ad8c6746fa86dbe5dbd7d9543ae63aa538233c325765f946b941b8548778ff448f6ccda9fed55e87ddd67d077c6efb53fe7a07c8059a057d0b1e072d2040515a21b4a0e2b0a9f3d04542bda49fa5d864c5438b32dfa774c606cfb61fe787ac2dda3868915c95b5292523969d4e39fd269191f33c34e7dce6264afe51ccf53cb6f39e758305f9874d1f6924709f5f289b28e8ae96a74ad497dd4b5aaeb2f9ba5d8216da51d6fba77f744f74d0ff00f058d748c23267d6e5dbef361c66c3673fec603e1476e8ba94f879f7d7b29f197dd1be2bbd80f499f123fc77cf55d565879bd7a714d73bd6683fb1771f3e0ef140e0700742104cec3087c45c823ec112cc465c424621589473a21139035c839140aa5870a42e5a34650eb682d7420fa3cfa361796cb8a2b91ab87eb27c614138fe9c502d61a9b89bd8793c585e13ab9b9b97db9af72ffe6f1e269e6e5e5a5f00ef0a9f0a5f2bde077e4ef16c00b6408ac0a860b3e1172131ad9b2734bafb0a1709bc82e9101513bd119313fb1d7e231125889524913c9fb520c695ee97a19679915d9223973b90ff2a50a8e0a6bf8a6ad118a0a8a8f95ca94835554553e6e6b524d51dbb75d6efb0ff53b1ad59a27b542b46d77e8e848eb627457f45eeadf331832ec376a35669bb04d5b7676edeadb3d6a3667fe6ccf7b4bb4950041d5dadcc67f6f826da15d837d8b43bde355a746e72e97b17d33ae0b6e4bc4af1e084ff9fd5a5e26de9e3e2cdf4cbf72ff9b079e076002e5831c832348d964366531141b66141e1271f1d020f51b5d3d92c42889ba1d0dac5d31b1b1cd71dfe24d12a28e341cfd91b427393d65ea187f9acbf1b213df32b44f9eca5c382d9fc5cc9ecac1e746e70d9e153c175c3058b8f5425191c5a50f25459703cac4ca672bb3aa9d6b95ebde5c6d6cc86a0c6c3666cbb6aeb42f767675b7f694f4a5f69307fd86778f1a8ce327456ef1df5e9b5e9a19996d9faf5d38fd90fad8e189f992f8b36f2fe65e35bc3ef676ff7bf90f1f3eddf89cf1d57959606572b560cdf927ff46cf269dc30100538883365841e823a2102d8815a421f230b2178544d9a2b251f7d10a682aba930bc3e5c555c7b58171c1546138586f6c134e08178dbbc76dc05dc283e561f22cf17af00ef399f175f26bf2b3050c043a0409825342078596b79c12d614be25122b2a293a299628ae23fe5ca2509228252ab5205d20e323ab24fb4dae5b3e4f8184d7dfcabbf5a3e2a852a572864ae4363f552735f3eddaea1a1a8a9a4a5af2da6a3bb4748c75edf4fcf4230d720c6b8ca68d7f982aecb4dd15bdbbc1ecf51e790b7fcb4b564bd69a36c97be7eca4ed590eb34e2ace675c3eba12ddaeb94b79247abef5b2f7eef255f4cb3f803b9810b01c1412fc88ec4e990e7509bb19617da88f66416f675844f547ef618dc5bac4cdc60724bc3f1a9fb8969c912a73ace5b8ed89e90cd2c9f553c5597ad97339cc3cbefcee73c482cdc2e28bc245b1971e97b8960e5c312aabab90af2cafde56535fa75dcfbe66dcd0d368d134d262cf7edec66c5fed4ce8dabc71b657a5afb65f7de0da90f670e3a8ced8b50981c9ac5bbcb799775ede759b699a1598a3ce4f2ca83c487978ffb1e4a2f7930b4f179e893d27bec87879fdd5d05f8f5f73de8abd537aaff861eb47854f727f4bfdbdf9f9e997beaf25dfc296759797579abfd356f1abf33f62d6f8d62ad70dd66ffe74fc39b961ba51fb4be2d7d15fe39b629bc19bd737377edbfecefdfd90a3c4a172da391c80e8107dbd3f0e434a0e80e70b87f36e1f8000016093c4e1ac9971381b2f0130750067c3fff85000008c1040712600c05073ed7ff341ff017c3ece4feba5845a000000206348524d0000592b0000595f0000deb900008399000070e20000c0e400002f4b00001978d02b64290000042c4944415478dad498594c5c5518c77ff7323bcb40ca32436929ed80d2c5071393425994362969215553496c6dd3343e92b8251a1f348d892f7d50138dc697be9214ab95124b09d0326030b46a84a235b294916518d619665866e6dee3c3c4a1bcc89c312afe93efe5de73fff7ffffbe73cff9ee518410c431d5e1c2ebaec2ebae26f47b01db1da9bb26705475e3a872937f6cf8cfcb4adcd48da79a3105d3c82daac55909f6d26def09ff2f30dd03beb136c26941eaefbeb061aaf3b90fc9010a2b5e657d00d60721eaddfea60c0e301f02f31330defb11b3c0d1af5e4bb9f4d2be2a166fd4b337ff3cfe26088f821604c1f60f2d18d3bbd20759aec3cc3c9843a44f2aa2bdee630ad31ba13bf10c897fb11a8acce06a185ffec44060d8852117c2124f0b79570b0195cc3481aa4a3eab48e8324d42c0e732a0874dac79404f2e7da7dfcee6e1b4814b2ffba9ab58dd346a3da2f0d9b534ae77db08ae2a988c507b789537ce2c936addfcc2bfe24918ba0774d5a402108d82ae241e1aa0c1d0b09187d30654155afa7782b5207e0f0ddebf62a7a9dd464db985cbefeca1fe780e2d3d563eb8b61b8431311e195dd1686cfd88994a6ea2b7f6da301be1d993b95cfddac784bf80022601f0cc18f8a6cf426d958577df2a85e014358fcde1cc4ca5b8d4099919303bb8254f3288554a261bba023a44c270abdf4cf993664e1dcf420868b91385141be830369502c0c1833910f2826f00825e2e548c72649f17966712e291d6163715950c4da1e7272b81159563cfe451e20851e810b4b6cfa35b0b405358f0c74cd96c29b0ba049ab211f3a3b03c9b108fb4b6f8f4138ae40709ad7d1600aedf5aa4ad2dc4ea9a8e6f29c2772376caed2a0e7b6c21989b8f40448b09047e183651bc3342ba35b60a6ec58322929c7e8f663181585852f8f6672316b3c2c8d82a43630a51618c755b1dcb60cda6c419c56880ce9e45a2584083f92595c64fed9cb99c8d1086847864b56d544a97cb44db3d2b9aaef0deeb851c7f3a13740d80b38dbfd1dde72770c1c90edb1ca78f4468ea5ee1f9378dd41e4aa36bc044380aa74e3a515c0768fbbc7f4b9e0c7d2ec94a45e4a2f59e05b3112acb32c1330483b761f03627f6cf130eebdcbc9b02aa91576afd9cafb3b214d0b9d2610583898b0d3bb878361fe62712e391d4166b68afeeed244baf914a8500f69781d5063fde89671880bc3db0ab043c0fc0e7014541e417b3647192956586b5102ccec0d448ac33d98ae7fb76b9322daa5d8af8e2f19ba4aed722ed2a09188d1089fc93cd1f84cc6d06325cc3847f0555935afd9282169517a94a8cd75320c335ac52d4d04c980ea9bd407ab3fe1b21a32b4c07450dcd065ce7dc8c7f791fff808a4a0dff57e874613f701fd739f723bff3654daccde6a2881aa976ffbf861020942e2c393eeafb5edc7c460130d5e962a6a702afbb9ae0f8ee6d6f28add083a3aa9bbcca5ef28fc60f5efe180026b7b96a2d80c8d60000000049454e44ae426082', false); + INSERT INTO public.license_label (label_id, label, title, icon, is_extended) VALUES (22, 'RES', 'Restricted Use', NULL, false); + INSERT INTO public.license_label (label_id, label, title, icon, is_extended) VALUES (23, 'BY', 'Attribution Required', '\x89504e470d0a1a0a0000000d4948445200000040000000400806000000aa6971de0000001974455874536f6674776172650041646f626520496d616765526561647971c9653c000004374944415478daec5bd171e23010154cfe4d07d081e9c0a402e800ae024a800e4c07d001e9c04e05261598abc05001a7e7d1ddf88824af644931096f66c70cd842fbb45aed4aeb17e617632e332e532113213a9c85e48deb6ff64088b9a45c4a2e3747528a36e33e2bbee45238545a2585f8af5e295e06505c66155f4a04e677f6058acb2c22f8d458f740f17bd9d8283230bc3fe272e0b2203f10456c369bb1e9745a5f2793492d52f77f3ed7723a9d589ee7b55caf5793fee5a26f571fa31e9938b9e572793b1e8fb7ae401b688b1369e21b9c4f093458513ab0d96c6e6559de5ca3aaaaba6d2211954b1248cacfe7732f8acb8880458422216a5be230222e4cdd14599651aca11309ad733e8ee35b5114c69ddfeff7b5c52449520b3ee33b1b6b401f083e21b22160dfa63c3a600a9df9e2371b10a6446613dd39571ed6d236776d2c8a48c2c624c2ab5c2b0fc08b5356115b1048f8e40f861202765c465266c6e33a38198d46bdccc60e8703e303a4bda58d804417e5bdbdbdf556f97fa1201f20449f0a4cef13a87b02b6aa27d334adc3d92ea090d795603c8f81d240a963a29a3b58aa5c0531bab51bbfd9fa17437f204da38fae3db32ab69791e03aa06a21bb90797ea76b332592e3d3aaf6faf8ec032dab4e4ccaef43c4f73ef3060d01699380c2c7dcc7d4314863a5d3a2ebf4d3f882b219f34b6fb289cf9b408cdf75a7076d74f5399af6c743b16f2fc562b1e8b4245d2e97ceeb7ad736a083262e980d4570f0794d4c92de073d54602b4e8189d202340f3d1c34015c6d01a3ef4e409b0548e951eddc3e2234ba4c86160f7d2702d890fd703c097812f024e049801438a4fc2ed0e902024e3f9880f30bf20dd5e662d76870b7dbfd97ccbcbebe929ecbb2ccd91e2180e3761d01b92c1f00011e6370dbd0d53501271448ccb948b751793aedb4238301ad1ec3f5ffc28a148516aba1b0002970d0f0e8c0e86baa4c7210705539c296fdf58700fc906afeb3460166904d5146dc060bb4359e36e300e5506fb7db871e7d8df97f9adfde0f46425a40cbe8973246bc1f8d852460bd5e1b1f8dd53108f3706e1f9a009c34317dc98c1209f350bd119280b6035846a82f56d606a1e12eab826f0208455305c579e214c14b898c6f026c4a645498330f4552be0820164f1a1753a76d24984e071f04106b059565726dd9096203e50121cedc102e53b337ea7dd44c1471fe6ab5621f1f1fdadb44b66b55414eaa1077b1449a02c515be4b658d488019faaaf4b8af394060c602578c93df15c079becb9aa2bf80bf2156897b7b8d26d2e50bb2f01905165daabef02cda208e7853797271f4c082082c27462922aa37e10021d89d513943383fec21fe7d65e6fdfdddb46fc8f07e85c83413467c832490542276098aa82d5608244766f93e802bc4ec6bde1fcc8425f606898993ec38e2bd52fc1e63b1c7e8f23de242b43976ddd941003f01973f15d7115394e4dc85ae17b15d7f12d7abaf0efe1160005144fe5e042ce31f0000000049454e44ae426082', true); + INSERT INTO public.license_label (label_id, label, title, icon, is_extended) VALUES (24, 'SA', 'Share Alike', '\x89504e470d0a1a0a0000000d4948445200000040000000400806000000aa6971de0000001974455874536f6674776172650041646f626520496d616765526561647971c9653c000004ab4944415478dadc5b8d75a24010461bc00eb403ed403bd00e4c075e07da81d781d7014905980af02a402b502be0f6e3418ee4606766990572f3de2426c2b2f3b333dfcc2ea3c02f4d0d2f0a5e199e156ca38be187e173f1197c0bbe1141e8a3e1c470a6c44931e67cc8826f0dc78a42db94b11d92e04bc36907827fe5b46f454c3bb238c5711f4b039abf0f40f82aef5d041909af0f0dff34fcc2be210c83d56a152c168bfcf76c36cbb98eaed76bce97cb25389fcf393f9f4fc9fc903936869f3eac1e4a22fb76bbcda228cada12c6c0584691922039f7b1de13aee0699a66da74bfdfb3fd7ecf55c45d53092167bd2f974b2f82d729024aee4a09a4dbc322a7d329eb9ae238e678436b2558859f4ea7599224595f046f98cfe79c9810ba087fb40d8c0763027d137349c452e1d7df41f82a3194b057097a43145ea004563c38d9029eabf0c810c7e3315bafd7b5eb16ffc3774875ae71851113c8a5b0b469d0656288d6489152688b00eb925da004223b580ba8c6e20696914e64b7dbb5c6f850049428553a5145caac0fb7f2909e442c3500110f6abd206aba4162011fc25761b6d25248eab07eeb878210c87c96bd124fc0b5dc8cb06bba5082ef11b4baa8fdb91e4978c19184bc88de1242c0a2268f09d5a53afccdadf6f01c6e3ab6c482b40a7c6a2f92a4218ef5b13ca8897363087729a09f6019676a85bd12d043ad7d098264e4f2fc7b2e59c6cab3c15ec3fd298b49bb431c8fe28e6931cebe11fc48a22dd6af96b52431859ba100c19ba0f1d8fc98d481023430293a1c0ef9752f2f748f14d76d361b51494a5d8fa62987d0906da0bc3beb9cfe24a0075e20ad258800c68e5390c53246fd179ac8cf457806a61761026f0aa094e02a3c37b80e42014d4a682b3c4701dc60dd74ff58ab773e994cf2a06494f0b12384bf2d016830a4e201554f0086d0ea16531e8014378825e083287ca111031a97003629fb26ad39d8c61917677006a9000ed0e100368b2cd7717120c91965958870341a3532679275f4fafa6afdbe0cb81461bbdda680b3f0a67ff1e46ce63a01abf56fb75b6beb13c6bc58cb6149cf5f2b5895c481d8dc4c4395c3a146094b556e927e0067fb9b5bae1399646a6d89a18ee692a5e4fca4045b9125d8fb677b14a725a6d214e57471aa753c1406214ac6260aa7a728318ca429aad216e77881c64e11772949dae2d68d1109ac75d907e4b2a4b822ac9f8ab6c624fd415f3b43d2ca92d897dc8a3747b94547b520d2129e0a9e5a9ba3eadbe3509ae07c5fe3264a97dbe3ea0724caf37ddc085f063adce3f22c62f9259ca3b20046d7a66e31f037a0251a20522a8fc096c761abcd14344e00a9cbe3b42e842ef2dbdb9bb5416cf8f77f77488a09a0c487a98f9a81a9e7b38271e0481115a4a4458e76c788217cea7a50927d425c1aa935889961ba392f5c2e892ebc015667620cf513e309b750f1717e18f1865b296a0b5f5542c4cde5b012b6b8db640bdc8b3184a832a9d6f9daafcc94e7090e921b801d90dfc1c8f94dad2c6084c7e3f18117dedfdfa573fb65f887af5766be42e621bd34752f7a1a9d524861858e3892b8bc2f6fe8e3fdc1b878f660682909922d2d3e28c1eb4e9dee02fd97a7773e5c7dd4419c58057f5f9f9f149fad4563f0f9f5f9b3cfa8fe478001008252cd9d372775a60000000049454e44ae426082', true); + INSERT INTO public.license_label (label_id, label, title, icon, is_extended) VALUES (25, 'NC', 'Noncommercial', '\x89504e470d0a1a0a0000000d4948445200000040000000400806000000aa6971de0000001974455874536f6674776172650041646f626520496d616765526561647971c9653c000005024944415478dad45b3d76da401016760a77a24c876f20a5730737801b403a3a8e00398192130027c0a53bf009049d3be113002750f6535679b2bcff3f923cefcdc37991969dd1ce37df8c865ee0570644474463aa8f544572a67aa87cbe075f4822a209d18c68ee4833ba66d465c3a744538746f334a5dfd529c3b3060c679d8a561d81f8deb76038eb441887c6bde17d0ba22f0a80c695300c83a7a7a7e0f1f1df12b7dbcd74a9ef44e7f4ef57dd9b7bbafb26ba213ad13174329904711c173a1a8d84d71f0e87e07c3e179fd0f777ad0470a459e7e6e3c887aa20478ccea7d3699ea6696e2b5863b158146b2a86c4c547b688e8c252c397cb657eb95c72d78235b1b6a2239c3a41c9f8f178ecc570962370229a7242284b717822bbdd2e6f5af6fb7d3e180cbc3a411af35114e55996e56d094ec37038547142689205d64467dcb888a202a5fbfdbe91774b942f65369bfd4f89ba827bb7dbad2c3bfcd06577c2276f1bef00b4ea9a38d23682ac2339094b96a1771c86f7dbd793f7259bcd26204e105db262e101cb01309e691d019d4e1a5f7502c104e12532070c792c0f8ceef9f9b9b3c697823de2417124ae1750f70c0f3191683e9f17606323a0b8d820144fabcaffafd76bf0f6f6565c03279b3afae1e1a1a0dc02508413feb0fe6328231700bff57a6d4467419474aa3cd3ef2a4542969860b153dd1c08882ae5851136e52ebecb2443606f02da9cb2905f7b7365d1c32343b6c657d58470d5536d4da37a7d6fb54138a2faa4b06157c69b8682e41424550738ebe5819a62c30ac4c4abf10a0429ab727e2e10f9686321ae61581543707aea753f36efa29f20d84b912fc7bcf82e8f3236a2d190b08ae7b2dc85ba12c1de8b6cb0e4d5f7ac868442092ad5a6459082933bda43fbcc16e2f8c3bf414c56ab554154c8f115b12da9606d10a1a644d0878cb900a8927b718d423d2ec4822449bc7792b04f11105ae7dd12274c1d811845ccfb6aae4852b2bb38d56c5c72f9848b6e725d1a71409d05da002642cb65afb17107b8c4893a67f8520ea8e384697894ef1c4c71a27507b48d139d71405b38a1ed8026fbfdc009dd86890e4ec8d2a031116a90b058e1848c0831871cb0501be2aa02ade204d826e7ba3db718427cb98a739d1729ae4b70d82148c309b71c764586aac753d6dc9085800d4e88cae1d0473786d70f2c0ba02ac6e0a8ca5223ee735177b01a225c20acf7046c8db7d13a2639e01399525354371d4a5a50460a5cf0c02712a5b6b8495f4e80ba46a94d95f569d61d91f28b1193f21427c7a608b219c0c03d12c0ccb45e8dd9a444139657323b1b3179351688263f5d10233803eb40eba703a186d071d10c91a4d34c361196bb0c85a6264414df067d7afaf5f98093683a04dd557485bb2a78c58e3d0ac66e312bb4954d88609484692516c6d82bbea8abc69f4e27e13c95ca88cc4d341d862fc01775c9098ac6afe809ff20df3817bfd21b56222760d2c374b4adfeb2c2741d45e3318ff7cb64fdb58ca4b4d137a8b24e0506988a06259d4c88b7d13b50649bcee685a54e301d65f1d866773a31aefc5b016cce872370dc35cae02cf0f40bb3b50e8fc731b569ae82d480126bd6145a31df3370c292971d44e3b5406a203d5e8de3b38efa2058a51e8fc762225582ecbc49d09f4da4de61d0ce4fe544f13e6e9a7f84b4a9d0b6f13b9b34e742a2a09ddf0feee949ec8c0c75a64d2d9f78a70c674d9d2e02b7bf234ee99a03d79bed358013233a8c84cf7e3998241094ac57cadf8ff4f3e66b837f051800dd4b1284b44b8df00000000049454e44ae426082', true); + INSERT INTO public.license_label (label_id, label, title, icon, is_extended) VALUES (26, 'ReD', 'Redeposit Modified', NULL, true); + INSERT INTO public.license_label (label_id, label, title, icon, is_extended) VALUES (27, 'ND', 'No Derivative Works', '\x89504e470d0a1a0a0000000d4948445200000040000000400806000000aa6971de0000001974455874536f6674776172650041646f626520496d616765526561647971c9653c000003bd4944415478daec5be191b230100dcefdc70eb003b502ec403bd00eb403ed403bd00e382b002b800ef0ab00ad802f8f89339e9784040910bc37b3a33348645f369bcdeee210b3f0a84c98cca88c98c89050b95189d877c83f6211a0f49e4a4c25af496236e6b8cb8a2fa984352a2d2363d925c57d2a69038abf4ada36115e43335e26611b4b03cc671d50fe59b6551471347fef5239505929dfe0ba64369b91c964527c8e46a34278b85eaf85244942a2282ae47ebfeb3c1f768e0595bb895977753cfb72b9cc8320c8df05c6c05894481d273936b1de6355c5d334cdeb469665f976bb552522ab93045765bdfbbe6f44711e1120b929124acd1e33723c1ef3a61186a18a35bc4d825479cff3f2388ef3b6006b188fc72a3ec1ada2fc5e3630fe180fd036149744a8abfcdc06e59fa140c2b616a7d745e5354850f2074799c3ebaaf28a3ea17429f83206db74783a2494ec0ed20394f0708320c416608b2c3945eacd3eccca3694f803ae1504a21bc0a86d28590a312fd617c6f6b602cb567547588b7ed8447cdf921520d023034600f77c4f0f38c2b3bb0d180e8764b158882e2f1e04b82c6dfd0babd58ad80e09019859cf6161ef37ef17d4840a1675806cce66b331a6d0e17028b24bba9620c82cadbe44b30ff3d7551eb8dd6ee472b9182300e3eb02a9b8f3f9ccb58201abd8706fea0b24bacc40c0b0ef0448964c6101dcab367b7f0d5d46830a37f58980a22e90f32ed03882f4098ec32f810cc887e38f804f27e0abee0151db3b9d4ec61e18e179dd0ebad653604936e66da9929b802ea2f1b00412d14cf605125dae20801b5ca334dd17e080262320d2bcc93a482633112e01c1e9a97704382c21c25d064110c8120ac2e3aa49ebc1c146e7988e67994ea7b2a448016e15783e9fe7b643921eff511ff8f8a4e8b7c84676bb9db56b1fe9334993d5af684d5818b1a126a839fbdcf298b03486fe1fdbb05eaf2b154885c5d1fd7edffbe2a8d40ac88794c7813e3748c42aced3da1619c42da4861619c0aa2629c54e31ed66ead236b92e04498abd8261d5582220255da26d364fc0292b289f928a8d920f7f50da24dd46ff10b6e5265a659549c04c34610d98750466a4858e71a57679786213f102fc8d629778edca3f9310a8262f314be8227f67b7c0bd184371c69ff77a4fb9625481086c275a4744ba3c8a6a3304c90c51e519999b474205df2bf419e08487ee0c23afccbc86cc5d7a692a63398d46e196c50a0d49a063f2a6aca18df70743f6df9d81afe324df9cf14e29fe0a8fadc7ba5f9e5e9b3075a7013f0197ff787d7e48042d39cf996cf2f3f5f9c8a457ff2fc0009a2c4cd8368e16c80000000049454e44ae426082', true); + INSERT INTO public.license_label (label_id, label, title, icon, is_extended) VALUES (28, 'Inf', 'Inform Before Use', NULL, true); + INSERT INTO public.license_label (label_id, label, title, icon, is_extended) VALUES (29, 'CC', 'Distributed under Creative Commons', '\x89504e470d0a1a0a0000000d4948445200000040000000400806000000aa6971de0000001974455874536f6674776172650041646f626520496d616765526561647971c9653c000004404944415478daec5b8b919b3010c5d700d701ee00776077e074e074e04905a403d2814ba004ec0a7007beabc07605448f810bc348bb2b217ee7eccc4eee2e42b0ff9fb40a868548e14ee1a6c6758d145c153e149eeb9f819fc1822056982abc292c3d6151ef19cd99f083c2dc23d126cceb77cd06f69ea52dc5dbd48c884692b84423e229d4fd3e03e2db98b810b2b25c1f2afca3f0a7f881300c76bb5db0d96caa7fd7eb75853af8f8f8a8f07abd06e7f3b9c2e7f369f37d881c3f143e87907a587b6391440e8743996559d917b007f6b2f40df124c42b6997499294f7fbbdf40dd8137be31d0226dc7d324144fc7ebf2f6fb75b39348011428df0c204967848c487aadb429ee7126de8cd0432ccc5713c8ad4296dc037087c42e8427cca113f84adbb80c0247297ec6e11c45b3021b1b1fbfb9288b76042dc4bf5a3289a2df10d303e81358588e2605114e5dc010262a2c3c1c9eb2309590a20443251c14efa50aba501e30fb45a70323d008e2e0d909f10a690eb3cbf76f176bb2d970a305b420ba26e7dff6da4dfd6028201c73603b2beb60fef9ba669a531baf08962495a3380e9b0613ca7d34848569a8213bea0683340bbe8743a895e8275c232b562aa299c82893a06f6894c603ab14755236c4d0b24498f65b3e2ab82ec3201bf4b99d86dbc70403c8f94bfca919d9c1f54deb58707621b3506a375ea2ec5e3f1487e27cc8faa0f3217f5621c4cf552ec818f3349166b005843a5dfd807489907e51388685085436dc383735826d5d7a977b76e6fb7ce8036ea0d7fd3d5542e5211996111b8e6fd26a99a1c67a3e6dd9e6197a0b6e429894a08176aabfe3f2800735c9eb3d124f8179f60fade379756d1e3f1d0fe5d49c57a2fcc01748039c218f016cc14dedfdf5f9b01262d7b19068c056f3ed5f372b99092d3d9bb692fcc055d7c870b780d83540285d085f0d60e95a66c92ea3f36711d114452147161d07b22a48bcfdd6c0c04e21dd4c7e912215dcd807554ddc2254283a6c240539edfe4f054418567b10efb504ca73481a85972633124e905301d17b62c6ea4c6b4af58e4ca7682c1c924e5b06eb8e25a0e4b7a16c4be7b2f0d11a898f4e3297b0513a40d119886a416a0d2f6765f70f09698d463732d31f816a9606c5a62dfb229ca4c89d2ff6df1ef3c1861a45f98cef85a3727e60a549b8d1a90bef47094d482a59882a0c3cc9e2f4e6dfaf90b3b2051fc3f222384ed2b1f926a20719df18d6df382f439776d9664dc786b4ac70801080e4a16ae0725c5e7841173c73609e15c729cf3c2ed0ecf18315e20f5694e8c37b5c3108c00e116670706bb3390491b15cd1cb0cf616a3c0b55b71c9d5bd9fcca8111880ebfad6e5745d1d7b519205ae1ddd157fbba4c7365e6f3d3fabe24aef3fc1a639eb00da6b92a47d9fb7eeca14ac81da91f09d33e61ce074436bec12366c1ccaed2465453c5a3aa9f8299df210eeb9233f32cedc310aabe1ac961ee827fd7e7b9930f988a62c27aad7fbe0cf9717f051800f651ef6054e44bf60000000049454e44ae426082', true); + INSERT INTO public.license_label (label_id, label, title, icon, is_extended) VALUES (30, 'ZERO', 'No Copyright', '\x89504e470d0a1a0a0000000d4948445200000040000000400806000000aa6971de0000001974455874536f6674776172650041646f626520496d616765526561647971c9653c000004704944415478daec5b8d71ea300c362c001b840dc206b0016f03d880be09e04d001bd04e001b0426081b00130426c8f39726776d706cc9719cf44777baf6dac489e44f3f96949e689602c953c9e39c4739ebe82cf92ef998ff0ebe892f44a1e48de48be4d411c7f99a4197059fe72f9a36cc71feacced0ccf16e53f9d2b62200c7a805c1cb1ce566e7959692930e08ff91573682f498d70f246f252fc8370c06623a9d663c1e8fc56834ca5845f7fb5d9ccfe78c8fc763c68fc783f37e881c7f243f9ad8f501c7c9cde7f374bfdfa775298ee36c2da9488e6f085b111e2fb95aadd2244952d78435b1365111894b2590849fcd66e9e572499b26280288f0a504a3f0d8111750e75214451434d4568236cc8561e865d77568984c2614250c6c84df98846fc2d66d88601231370c22bb3b5426fb619885a8e170c8d66a11e6aed7eba7bf2334224c826d68b15888b7b737dd256bc9ffa8769fb8dcf9c283074160745eb8c63692109010d6823e5e8efb62705614c155cfc2bd5c32f884887294ad5c0049098776bb5ded14176b70d166880e732baf0f58fa16de5609408e2153e4ed3eec9e43088d8cd435a56498dc706bf0074a14ecaa6ee0da222136b3196b3a348548e5f92b535c87f0fbb4ab100a4c450b772360b69af5827239cbc9432979fa66b3b1f217589b6b8ad4fac1de15ecb8c27351c30dc39a0df9941d3af1be38149962bb2d6a0ae61ebc0cef949d1126551770b5bd5c2ed9e1cc00d327c633b8a4f131b37edeb87822097f76ae8f1c5f47288b95e9e5e5c5e933a8cfcd695c69ffdcc407c4853fd5f6cbcc254d3488fa55ad2a8dd62a0b9a3a521542d7ebf573ad3d0832f4d57916439661bf8001e5855dc2ff703888d3e9f474ddebebabf148cc35038d2ce3bec54d4e4865fbd87928caa6ce60a900d16fa3a5845dbedd6ecabffb26ef0a80fdaa765f86b7c651d709056cb7dba76e0fba472a87f8ed1480dd8702544a716df79d5400a05fde7d145851d06c8bfa2e332e53e852556d55886802793a8a5d1c3a2827416aadc15450b129cc569d088180ab0b04706d188eaf6af7cb3d83bacfd2c872870294ff45e3834ba614b6ec0f54610fc2ab72049b67101470ece743054f8434959b7353bb3ac8f7ab4e8126c5db748e346b9eb5f540d70511cabaa6260ad737a19741a90bee5d94c3a98eb08a502e33dddb5449cc6b515455dca408cf2d8a1a4ae31b52599c5b18fdaa65f19fd61889bf7d6bcc50a09dff36476ddae35c53f88aed71ed800416e6ce08f81e9080b90a8b59a11f3b22f33b2465630a0512b8e6d0d4aca001f6a4d9207654102d4e897eccf30966150bcb41c9c21f18e78411737d0f4d1ab23cbff3c285f3f28106440602e4db99182f52d6261401c119297663df0c449c388e135e9d616adc8b35987904cbe67b168a40746035f551012a3e9901237c96c31d4263f993195d69acaaef22f9af8f72fa4474eba3a924cf5dbcd2c0942b78e24d9d30e78242d1cef7833bd1b14f6927baa28a43a8774e7095692c1da3629f1f639d43bde7411953f13e86839f43513192f3b18c2fde3f9f3fe7bf9f9a7cc1ff020c00b8b38128c9ee68580000000049454e44ae426082', true); + INSERT INTO public.license_label (label_id, label, title, icon, is_extended) VALUES (31, 'GPLv3', 'GNU General Public License, version 3.0', '\x89504e470d0a1a0a0000000d4948445200000020000000200804000000d973b27f00000002624b47440000aa8d2332000000097048597300000dd700000dd70142289b78000000097670416700000020000000200087fa9c9d000004e54944415448c77dd5d96fd4e715c6f1cffc663cf6d8c62bdb00b62103b6716d106002c62d04255237acd2566aa4a4adaaaa7f01bd6f2e2ab554b9897a59a98bbaa4a9aa4895825a2ab2101a08e0408bcd6a8cc0061b30d878b03d335edf5ec47128697bee5ee93ccf7b2ecef93e319fad948c1d766b55af0c53065d74da3937e49f6e8e3df54edae280176c2ca95a96ac88a550900d9333f971fdde76448f99ff6dd0e045df4936ae4feeb04ba3554a91735f9fb33e726b66a6cfeffcc9c07f995b64bbd763d94c38144e848761213c59f3e16138110e854c8865fdd176d167e5fbbc9b9a3b108e85a9104208e3e166980e21845008f7c26c082184c9702c7485d49c77edfbc422be68b0cd4fcaf67e3b7e48ad01b7dcf077d76d95c4a85f199675cd800acf125dab9fdda0c7dd4f0d1abc92facacbf1970cba6741d68831ad9a4448b8e5b23c4a64ddd72219f5d5cf56f948f66383623f88beffa5d4cbee6ab6d99871092d3a9480847a299139799b640c5a2b1fdda80fe3bacdc7b1c38f36d6fdd0a45669c7957ad656eb8cba8f94bcac5a2d9a95f8a7159adcd2e272f168da194309a5ba8a377d5ddab08dde93b65791111f98526456ad017129e53ab549e8f6452b257ccdcf374d77b918d9e8f90dc903a6a4e58c6b5364c43155ba1cd4e18cb7e475a8f08e7119a5ee5b6ece01cf243d6f5364672cb3539359c50a22298f9d50a3d5843beea8d065c40519291f9a9534a3d88c26ed6219ed091da9aa5d6a149b9211f30f13c6f03783266c96f5c88c537a341bf15793b67a28526d9737ab721d91b665c94609351e48f902a62da8b5c6944885981171b3e28262cbecb4ca5d958a34aa48da12a9abb20a0d726eaab75ba443a7325f96f607539ed3ac47c16e9126ad068d6a14b35a257509e5a54a516dab0f4d7bac588b22cbcd98d4eff3f61b326f5cad3addeeb96a9b954829a53c211613434cab22175cf1821279efd9e0255b3ca34a95ef7acb294dceb8e29b368bf0b12e21972fcf2f9e458b6a8fad5570ce80061b3478c7512dda5df0a62679ab6416f73f2f4f2e61687ce5c8e245cdeb35eb9ae386bc68a531311fe8356ebd2e77641db47671c17920cb50c2a5c76dfd89fd62c8b9ec913b7698b14ea54bc61cb05ed6468d6af46b5e9207fdb2732ec5a5e73b57a69e5382b80ddae4b47b24618d65ae88f9aa6675fa1cb5c7ba25843df66bdde3e1b791b30b37bb5d5fbcbbb4b4245af51853a6dd4dbd265cd5a7a0ec0902f63b6be196eec835ef5f9f3ba2b044d56a431a553969da1abb5df5be1b46d4492fc90b8eb83ee7b8ab7133a6e7f68dd6ec54b748d9b80b1ad4396dcc6aab4dbbeca14a1dd62f1974fb99e19b5ed517c703d5a3edb9a27655609951d7345af08631ad92068df886e625920efaa9e3b9f04b6f988d63d670681cd81462db9522b2ca0391352eeab55acca0727b942eca47bde6f55078dbabee7ec2c45143d36d97d2d3b1269548aa57adcc8471a79d7756a7560970db6b7e11b2e7bce2fc93541e325c68ea4d0fc7d659212e2e6e415cb911ff92b4dc7a95669c77d8ef43f6bc1f7bd7c27f2643c27e47e3b96de170e809938b81321d2e87a3a127dc0e63a1271c0edb4222efa8fd8be33c156d9166dff3adf2bacdd11ebb64ac508c8231fd4e3be5cac2e46d7ff61b573efdfde9702db7c7417be375e5e53551ad32e48c1a5b98989cbfed84bf3865f2ffa533546bd3a9d3e7ac5682827b2e39e9a45e8f9e6efe379cddf27e00b2b0830000002574455874646174653a63726561746500323031302d30312d31395432313a33353a34372d30373a3030be1641b70000002574455874646174653a6d6f6469667900323031302d30312d31395432313a33353a34372d30373a3030cf4bf90b0000001974455874536f667477617265007777772e696e6b73636170652e6f72679bee3c1a0000000049454e44ae426082', true); + INSERT INTO public.license_label (label_id, label, title, icon, is_extended) VALUES (32, 'GPLv2', 'GNU General Public License, version 2.0', '\x89504e470d0a1a0a0000000d4948445200000020000000200804000000d973b27f00000002624b47440000aa8d2332000000097048597300000dd700000dd70142289b78000000097670416700000020000000200087fa9c9d000003fc4944415448c795955b6c936518c77f5ffbf1ad5f5b4ab7216c7127d846066c333bb1c112173c5c6036b30b41259198188877265c301363d44412d42c9a70a131124d8c12e38d0968c8300b07290293c8d898e28cb08aa056a05bd76e6bfbfdbdd8a97420f37ff5bec9f3ffbfcff3bccfc160216cca69a0856a4af001e38c30c0f7fcc0af24b28d8dacbb452ded3c468527b8d40a1836304154b1a9c46d86f996c3f433756f81529ee6396b4d99d540336b58891788f32797394b1f57a6a62ef3295f70f52e7ee3a29ecf8d68b976eb84224a2b1369457442bb552e23ca41ea712da4b7d16ba7dad5a398ee85987ad42e3b452f6dd912f5f4fa523b352447ff054743da295f8a5eeaef8cfda09ddaa5b01683b076c94e7190d2597a0e5dae68bb86164597a42175c815e56572a605363050a1a392d29ad0c47d8298c651558801368089978e9cca4e9af9895e463058c5a3acc6e11c3f22c060394d94f2373d8c524f236e36d2c9fecac90e06a09650954eeaa41e91572eb9e4d3365dd6a45e915fb63cf22857cfe8aace6b9d7c7a559392a4ef5425423ce4a2c9286f229ffd1c23cd2a0a18e710874892244e9a62f2b8cd371cc76182384904c05a9a30ca6974b1d10eb630cc310c9ee233dea3854a441a8002dee16d0a89f133498c8cc20dd28c1d64a3494dc05acd056e91cfb3345343094b28c102600945801fcdbd3c5f796b0858f15a93e220b9fc41927ccab8ce216240218f03708b8f8810268722ccace22d6019378a4dfc5e3c248125588cf016bf63d04c13060637f91007833a5a21cb071b2ff84d0c03132f0609e2f8584b8a3053081036d5047990add47261a67d67f3307d3289c7fd694ab08870894edee563bae75e59c1ebd4e263d95cef4c328a05e490200171936bb757dca48142aef03e13388448ce0998145294313c1c8e700d03832dac6414ae990c8ed6fc626e650b0738493f0e637858871f0787344e46d40e70894b002ca792688a4193d38927cee46de32552f410c5621d1dbc401eb994513cf39dd3d3ae684ece8d451f89514e1b34f2416dc3011af98b8bdcc0a2942a0238fc46180feb593a431a63706ea6bab9ce5e2e9ee745584ab79d7c4d894537b3244dea0dd949baa7d5db18ae56e87f0984542d86699bf6c7cb9beef1edbab268fa556d977b9cbd7867f353c5e180b3479145d123daa380c3d754656e8736ce059d2e8ddc973ea22e051dced176e74e31e9a02fe0ecd0d9998171f7d49dd50e051cfa7872416f61b29923ee789df6a9ff2ebb21a67eed539dcc0447d83c4fcf74c34515cfb3cd5fbcd6b58966ca79801c609208c39c21c490130bf3259f30345fa0d9cbd5cf263a79d85decf7e7b9f2f10171fee1a633164b8739c15784886512b2050072a9a19556d653800798e006839ce21417b9956dfc2f6ec53a03c4e284690000002574455874646174653a63726561746500323031302d30312d31395432313a33353a34372d30373a3030be1641b70000002574455874646174653a6d6f6469667900323031302d30312d31395432313a33353a34372d30373a3030cf4bf90b0000001974455874536f667477617265007777772e696e6b73636170652e6f72679bee3c1a0000000049454e44ae426082', true); + INSERT INTO public.license_label (label_id, label, title, icon, is_extended) VALUES (33, 'BSD', 'BSD', '\x89504e470d0a1a0a0000000d4948445200000020000000200806000000737a7af400000006624b4744000000000000f943bb7f000000097048597300000dd700000dd70142289b78000000097670416700000020000000200087fa9c9d000008c34944415458c3ad976b6c54651ac77f67ce9999ceadf442e96d5aa8564a0b95562a140a5b16f012244182bb62a29b266b4cc06c4282f1839744dd88624cc028f10388ae06814d40651502ac5c1a2d852d5b7a675aea3230b45c5a3a6da7a773aeef7ee8880501d1f84fce87c9799ff7ffbccf7f9ee7fc5f89bb4312300528036603d3817c2090783f04848136e024703af15bfba58da55f78ef4a902d0596005325494a73b95c2eb7dbed703a9d00188681a669b6aeeb9a10e21ad0091c02f601ed80f15b12c803fe043c0314a7a5a5b90b0b0b99397326454545e4e6e6e2f7fb0188c56244221142a1104d4d4d747575118d46e309f24f81dd40e42eab8d049403db81684a4a8a78ecb1c7c4b66ddb447b7bbb181c1c149665899b619aa68846a3a2b5b5556cd9b2453cfae8a32239395900d7807f00f7df45c57100d5c0b7b22c9ba5a5a5e2830f3e10172e5c10b66d8bbb856559221c0e8b4d9b3689929212e170380ce0205095e0b82d2a8023b22c9b8b172f16070f1e149aa6dd35f1cd88c7e362dfbe7da2baba5a381c0e339144d9edc80b807fcab26c2e59b244343434fcaa53df0eb66d8bfafa7ab170e14221cbb2999036ff66f224e06560a8bcbc5c1c3e7cf877211f2fc9810307446969a900a2c08b807b7c02f3808ef4f474b175eb56a11b86d08787457f73b3e8adad15d1ce4e61e9fa0d9b6ad1a818ecea12b1484458e364b24d538c5ebd2a62918888f7f509db34c7d66b9af8f0c30f456a6aaa005a80070164c00f3ce77038962e5bb64c5ebb762d6e4da3f5fdf7697ceb2dce7efe39bdb5b5b8fc7e92efbb0f87a230d0d141d33befd0b67933170f1d428b46094c9e8ce2f5d273f428a713713d478ea00d0ce0cdcec6939a4a4e4e0e6d6d6d8442a114a00f382127dae36f99999979eb5e78818ab232ba3efb8cc6b7dfc6d675dca9a944cf9c61a0ad8d8c59b370280aa75e7f9dce4f3f451f1e66f8dc397a8f1dc39391417261218d6fbec9b92fbfc4181961b0bb9bc8a143a8910869a5a5a4e7e763db36b5b5b5b2aaaa2ee0b80cac0056cc9933c7bb7af56a92749da677dfc588c598bd7e3d25ab57234912178f1c21909f8fa569b46ddeccc4b23266bdfa2a5955554892c4c4f2723c93267166eb567cc120951b3690f7f0c318b118170e1c0020abb2125f6a2ac78f1f271c0ebb81330a50a528ca848a071f243b3b9b584707b17098896565041f7984a4f474262f5bc6d99d3b19eaee0649c25455a6ac58c1bdab56212c8bc2279f44f1f918b978115bd7f1e7e793fd873fe00c04489f3993132fbec80f7bf6107ce82172abaa78e08107f8eebbef522ccb9ae70066783c1ea5a4b818b7cb85353a8a6518b8d2d2509292003055156159385c2e1c4e274208b4fe7e2c4d439265dce9e9c889b54863c34ed83600130a0b995a53831d8fd373f4282e59a6b8a404b7dbad00331420cfebf59293939398850e2460b8bb9bf0d75f234912ddbb7661eb3aa9d3a7935a5282272383b33b76907ccf3d4c59b10267e29b70ebc12e91316b16818202fa9b9ab0464608e6e6e2f57a255555f315c0e776bbf1f97c37c4f57eff3dfd2d2d0821d0a351148f07637898d4e2620a57ada279e3464ebefc32c3e130453535f882c1dbe6e09a30015f5e1e433ffc80118be1f7fb71bbdd007e05902449424a940e21c626535a1a29d3a681c381adeb0c767612dab68d8cd9b32979fe792459a6e3a38f38bd610303eded94bff4d275c97e56048703d9ed469826c2b2aecb04480aa06a9a96acaaeaf5000164575753f1da6b285e2fb661d0bd6b17cd1b377261ff7e32e7cca174ed5a528a8b69d9b489735f7d859294c4b4679f1dbff975589a8676ed1a8acf879c9484aaaa188601a03a801e5555b974e9d20d41ce40007f5e1efefc7c92efbd9729cb97e30b06897674a00f0de10c042858b992aaf7de236bde3c22df7e4b7f53d34f951c87d8850b0c9d3d4b7241018adf4f6f4f0f8903471c40dbe8e8a8150a85304cf386409190034076bb915d2e6cc3c0d67510024992482f2b23b3aa0a5355517b7a7e8a492462c5e39cdfb78f787f3f59f3e661c932674221e2f1b809b42b409d61188b1b1a1a52ae5cb982fbc736b22c6c5d1f7b4c93aba74ea1f6f6e20b06e9deb50bcfa449e42c5a84158f136d6f4752149cc9c9489284adeb98b118b6ae73fe9b6f087dfc3169a5a5e42e5ac4e5ab57f9efa95398a63904d429c071e07c737373cae9a62666a7a7230941dfa95334ae5f8fc3e5c254557a8f1dc3d234264c9dcad91d3b180e87c9acac0449a2f7d831d267ce24b5a40480bec6464ebef20ae6c80897ebea507c3ea6af59837fca14febd772fadadad00e780130a63beadb6b7b777fa9eddbbe569cf3c833310e0eae9d30c7474dcf09fb8efa9a728aaa9c1150810fae41322870f836d13282860fa9a35a4cd9881272b8b685717ffdbb307d9e36162793925cf3d47fed2a5f40f0cb07bf76e2e5fbe6c02478090c4984f5b046cc9c9c92978e78d3798e570303096251220290a29c5c504972cc1170c620c0fd3d7d8487f5313b6ae935e5e4ed6dcb9488ac2e5fa7a063b3b9180a48c0cd2eebf1f7f5e1e36b063c70ed6ad5bc7952b57ce027f056a7f3ca00fd800a80b162c1027ebeb85a969d71f4bd384b88541b12d4bd8b730a8c2b67fb6beaeae4e5456560a6004f83be0bdb95b4a80fd4ea7d37ee28927c49950e87773446d6d6d62f9f2e54251141bd80b14dd7260017f041a5c2e97bd72e54a71e2c40961261ccd6f816118a2aeae4e3cfef8e3c2e974da403d309f3bd87305580e34389d4e7bfefcf962e7ce9da2bfbfff5793f7f5f589eddbb78bb973e78e275f9ae0b823146021b01f50b3b3b3454d4d8dd8bb77af387ffefc1d6d7a3c1e17e170587cf1c517e2e9a79f169999993f6afe2f60016316f06765bf151c099dfe02fc5996e5c9d9d9d97269692915151514151591959585cfe7430881aaaaf4f4f4100a85686868a0a5a5854b972e99b66d9f0376019f31765f14779bc0f8ee989390a51ac8773a9d018fc7a378bd5edc6ef7d8e75ad7191919617474d44c4cb8307014f80af80fa0de8ee017ef6a094c4874492563167e3a9093489044992f02adc0f709bdcf30766dbf23fe0f708f107b7e1ffe120000002574455874646174653a63726561746500323031302d30312d31395432313a33353a34372d30373a3030be1641b70000002574455874646174653a6d6f6469667900323031302d30312d31395432313a33353a34372d30373a3030cf4bf90b0000001974455874536f667477617265007777772e696e6b73636170652e6f72679bee3c1a0000000049454e44ae426082', true); + INSERT INTO public.license_label (label_id, label, title, icon, is_extended) VALUES (34, 'MIT', 'The MIT License', '\x89504e470d0a1a0a0000000d4948445200000020000000200804000000d973b27f00000002624b47440000aa8d2332000000097048597300000dd700000dd70142289b78000000097670416700000020000000200087fa9c9d000003c64944415448c78d954b4c547714c67ff7313338033e50288332764420bcaa54a882582498b4b18d34e96bd336eebaedaa9b2ebbeaca2e9a74d1b44935b4606a1b6da2a98dad8b1651d0d80a54c40a835625508b3c8619e672bf2e18601844f856ff7f72beef7fce3de77cd76039327896ddbc403921b2807122f47085eb4488a7071b69772fe51ce610c546b6d7eb333d4082b83b13d7236ef13367e925b1b240016ff22ea5d9be9deca284ad640293dca38f3fe8672c462fc739c5bd27e48d41152d8c6dd42bfa4abd7aac59cdc3d198baf5855ed67af188af796e59e6983470c1722af599eecad59330ab883e5599cc04e7d98fb954a09a5f2da749e715d7d310d35935c87438cfee547a98939673485d2bbc9d0a571d3a28cba185d062db3e62bc4abfa454fd34ccea27558a313ec4372750c75f9bf5a5126ba24bd28c3ed72671831ab0c9a4d92c6ce200bdccb09e301e66b9cb2826213631c41821e23c48a9d8623f0d9c2ed6117aa09ace3c7da78bdaa7b08e6850d288de5758656ad5a43e50a5bed731ed54583b54a81d0aab426dfa4639e212bb4cf6122ea79a69220c70950830c2550618601c976106984c4edc287ff3180303931acaa0901a93fdf6861af27031b099e036628861ec94465bbc4d1bdff22a3e8e7292e33492cff3581ba9b3a9586797e2034cf298a68f1803c4d8c67f2955070932432e0605ec014094e2b3a31526057ef2175621877e46e9c34b51faa8e122402839f95bf11b844c023e02c9902d6ce32e77b84d2e5b93812b23131f649a1846723344163b19a5930861b2571530000c93689c6852c04b11312e3042117e56439404444dee4779b8a0598887df48508c7715ba784014ee99f44ccff62d98cc367298248b10c62a25c4b949cca1d7a43d31d1c50806426c613b104c7e422d7b7511c35cc319a7dde412437f721d8b0c3c0428c14f21d958f8b0012f1958c9023d0b67b846370c72d96282c2a93d5eb391626a292687325ea2183fa5d492472655d4b11930f053493d4160946374386ae51418347127a83639493b993795f9dba2c9cc9f133aa15cd1cf8b73d904f88468bdaeacd10d5cb56b9f98e2e3c55e9771cee3bea19b6b12e851b36c973394a40e55235d5ef7755d96f3547242ed7a4d1e970eea975abb4d335d1eb75eadfa7745faa85a543b473f8c9d3e193607394734a8a33aa3a1347b8f29a21ff48e9e1153fcc881856e2e49c3a484f778cbda1eb42aa9a6843c028828f7e9a38b1b3c74dc41da38c1adc5994aff4505d84b330d843c59eb6c3f3ec40c534c3bce38112e729acee4eead2000b08132f6514739f9048029fea19bdfe9e026e3e9c1ff030b55eb0b90e9adfb0000002574455874646174653a63726561746500323031302d30312d31395432313a33353a34372d30373a3030be1641b70000002574455874646174653a6d6f6469667900323031302d30312d31395432313a33353a34372d30373a3030cf4bf90b0000001974455874536f667477617265007777772e696e6b73636170652e6f72679bee3c1a0000000049454e44ae426082', true); + INSERT INTO public.license_label (label_id, label, title, icon, is_extended) VALUES (35, 'OSI', 'The Open Source Initiative ', '\x89504e470d0a1a0a0000000d4948445200000028000000240806000000fb3c781600000424694343504943432050726f66696c65000038118555df6fdb54143e896f52a4163f205847878ac5af55535bb91b1aadc6064993a5ed4a16a5e9d82a24e43a3789a91b07dbe9b6aa4f7b813706fc0140d9030f483c210d06627bd9f6c0b44953872aaa49487be8c40f2126ed0555e1bb76622753c45cf5facb39df39e73be75edb443d5f69b59a19558896abae9dcf249593a716949e4d8ad2b3d44b03d4abe94e2d91cbcd122ec115f7ceebe11d8a08cbed91eefe4ef623bf7a8bdcd189224fc06e151d7d19f80c51ccd46bb64b14bf07fbf869b706dcf31cf0d3360402ab02977d9c1278d1c7273d4e213f098ed02aeb15ad08bc063cbcd8662fb7615f0318c893e1556e1bba226691b3ad926172cfe12f8f71b731ff0f2e9b75f4ec5d8358fb9ca5b963b80f89de2bf654be893fd7b5f41cf04bb05fafb949617f05f88ffad27c02781f51f4a9927d74dee7475f5fad14de06de057bd170a70b4dfb6a75317b1c18b1d1f525eb98c82338d7756712b3a41780ef56f8b4d863e891a8c85369e061e0c14a7daa995f9a7156e684ddcbb35a99cc02238f64bfa7cde48007803fb4adbca805cdd23a3733a216f24b576b6eaea941daae9a59510bfd32993b5e8fc8296dbb95c2941fcb0eba76a119cb164ac6d1e9267fad667a6711dad805bb9e17da909fddd2ec74061879d83fbc3a2fe6061cdb5dd45262b6a3c047e84444234e162d62d5a94a3ba4509e3294c4bd46363c2532c88485c3cb6131224fd2126cdd79398fe3c7848cb217bd2da251a53bc7af70bfc9b1583f53d901fc1f62b3ec301b6713a4b037d89bec084bc13ac10e050a726d3a152ad37d28f5f3bc4f7554163a4e50f2fc407d288851ced9ef1afacd8ffe869ab04b2bf4234fd031345bed13200713687537d23ea76b6b3fec0e3cca06bbf8ceedbe6b6b74fc71f38ddd8b6dc736b06ec6b6c2f8d8afb12dfc6d52023b607a8a96a1caf076c20978231d3d5c01d3250deb6fe059d0da52dab1a3a5eaf981d02326c13fc83eccd2b9e1d0aafea2fea96ea85fa817d4df3b6a84193ba6247d2a7d2bfd287d277d2ffd4c8a7459ba22fd245d95be912e0539bbefbd9f25d87baf5f6113dd8a5d68d56b9f3527534eca7be417e594fcbcfcb23c1bb014b95f1e93a7e4bdf0ec09f6cd0cfd1dbd18740ab36a4db57b2df10418340f25069d06d7c654c584ab741659db9f93a65236c8c6d8f423a7765c9ce5968a783a9e8a274889ef8b4fc4c7e23302b79ebcf85ef826b0a603f5fe9313303a3ae06d2c4c25c833e29d3a715645f40a749bd0cd5d7e06df0ea249ab76d636ca1557d9afaaaf29097ccab8325dd5478715cd3415cfe5283677b8bdc28ba324be83228ee841defbbe4576dd0c6dee5b4487ffc23beb56685ba8137ded10f5bf1ada86f04e7ce633a28b07f5babde2e7a348e40691533ab0dffb1de94be2dd74b7d17880f755cf27443b1f371affae371a3b5f22ff16d165f33f590071786c1b2b13000000097048597300000b1300000b1301009a9c18000009ae494441545809bd586b6c14d715be33b3b3e3b5bd7eadbd181b5c1b080e71e40670c02950d92502d407aa9ada4514aaa4519b1f55faab9192082946156afab351aa4a11ea9f2a4de36daba0264da9a80c22d084d8450d260a8ff0f6738d5fbb5eefceeeccf4fbeeceac97e00527917ae19b7be7de73cffdceb9e79eb96bc5711cf1658b82021d8ae8025822824aa1facb2bf749855fe021497509b5a3a543018f0c09e5ab5184a2741eecf41d3f7fdc717a1d2b7fec73b569e4e7053ca5810e89e5e662d12220e4c27fc7584f8fca39f97d4b6dcb45966a91f45a4787e6f4f5d163f091b2b676cbcadd465d608b1ed49b145d2dc3263b76ca9a4e4f9b9793c38913e31f0cff0d646e4af94ec5e7f4496f2f75c9ac1796222dc9f540fe25c7467b79fd8ea603256b82fb8b9b8241bdc210aa5f25e3ac2adb11b6690973322512576627e29762af8df45dff3588ce2adddd9ad3dbbbe42d5f9207f3c95534847684b6d71eaedc58b3d257ee27215bee33482d1410055fa182b12354732229a63f8c0e8ebd71e3c939676e40e95640726971b93482aed5a1967077a863f99b559bc242f1a9a695b27c20471a8b165076609c0defdab669ebb74f8dce8ebd33b43b36327542e9ecc476674365d1c96ee77d4fb1e2c64d5959d9b6dabd8daf576d0639454959c98c819a5e5ad0ef39d1ed42450f6a56d2d234434b556fad2d83df2298b70ddb7dc1d3bda0e0eed63d0942114f5e067569e39ee6572b37857d60675aa6652824464292144f33daf0965cc2f6bc9a654a5978db500dcd0c3d16ae494fa57e03b95d3c30d0ad628dec3c39f9ce0723a570e9ea9263cbdaeb7f52fe48a8155b65d9694bcf9193a3c8379a6aa97ecdd6fcaa2a616856962c5893232b781bdbec53033e51de5ab533f46038abbca3e39e1c0a7ad0f59e853ab0faa987f617d5160bc7b26daca72d58849535d58607b4d45042a4a2f3297ac4a82dd68d9a22f082472d9bc73bfb1ff18ab9667163d01f7ca4f249b4238c43ce29e4c5820485ebbd606570a33f1c58af68d826d3c1138b713bf92039d352a7ce44a33303d15fce9c1d3be1085dab6a0f7fabbcadfaf98af5d52542556d6183a44357222433b6c69454b4bc780b88ad05b18bb94f24d57ea6142498f5bf10a55fad6c2b0a0738cd42a069f25030e0106fd83575fabf9373970f0f3e81854ee6e93e5bac145fd47ea1bd59d65a85230c59261eda062f3262fd5545e535adb5ad9873518061a1b2e8fec33225d21b9181abe10ba1216ea03417c8e4a7fa14c74a6498885f27397e77792adbdada742e967012bd89ebf1bf3ba62d655db7b3825d8aed2bd5855e6d3452b6303d5a759fa2f89420b737bbadae30768a092e339711e9447a90bdb1e198c253d9dfdf9f6e7b264bd28aa73f468a9107449ef285b51c555785aa6b4176455a5aa88e317057b92f41613929e78eaf0474d00b7029729bd0746d25b5ce18335217170ad60565942a86af9ef1060fd36d0b85f65a0ece8f95f23a2123e778ef5ebd2841297cb047aacc2432b7ec942d905a724bb0c5057c415d041a4af780d38a4baf5c4af11306c54adf4bf2646e08d405bead16e1e263c913e2ad09c242b5126961cd646eb1b3ebfcf99cee9c90db5894a01c7327256f24ce9a9349462092b4b7d168d8b606b7d838a92b1bbbd6feb5b67d45bbfcbec2b3cb36d7ed6cdab7ee4f65ada12066f070611d5a05cd3c5c082d5c24cca933e3e7b85604ff0a95c204dd495317a3675263f3d7a840d5142ee6ea42e24d5baa5e699835dbeb1ff52f2b7a550e28a238f095e0e19aceba07b4129fc9b422c9491fc1751a8206de37a3f3ff498b6cfcba377057ef9d55618211014b116a8e3331f7e9ec9ff179e217231b29f91b623b9a359f1156c27a9baa21308793fb2eee847c4100a293f2a8691b74a8c9d184885d988d4036dd8d8b084719bba8ef2a050962b2233ab35f8de163d77f3bf3d164148745477a497b5e445ce2880a6deeca6c7cf8d8b55e4ffbe491d137e6aec5b89d3eac8ceb18c9c17b7ecdb4e1d1d9739383e3ef0fbd42f94824bbbd723d4f415e5d902065e4c79cd722c7b936f541f4f9d827d3f4808e85335c1029c8611a498e248ee3fd637a9cf3e2227e3a3592e817199903f929642e4c63c83f3d3021a64e450f34ffb83910ee6caac59805d0cf8b967b1294338e1f97b7dfdbe7467f7ffbe4d8216c37f21796d355a6082d85ed8a5f88fd45cab66df4c91bb3e3a4e6afc68ff0460dca0a3d47c3660727c5e4e9f167a6ae4e1c890f259fd574d15cf778d3aeba9dabbe5ebf63d56e6f9bbd9a3aef4b90d66182941b3d79e3c0f8d1a117a6faa342641c038b2a891bf15b20ffae24b86ac016d188f4e2c87b37df4a5c8fc561886627337e5c565313476f3d1d1d187e8db2f83cdf542d27a6aace06e4843d8e626faadedd5c2af5f4c8a895cd82df6239ea3ec091bf43e48d63f4f4cd974b2b2a3e34bf933a54dc50bad91c9dff07c6c7948318c735deb31e7d838dddcdefcd7e34b96bee72ec9f43912b2f9a8e39e0e9f5f932a72d474f238e7b612c6e14191fb282348ebf7b3c3919bc50c6777a8984595390b54cbc79e3bae811ee511646685df847b59d0d2d729c3f45b373384fcad46d6dd85ab5baea071cff8c8cfca9e1f5e7d7ee9a72be9c231faec27cc1fc362779ef68dfa5dc23e4c92c567b862d36e6f5e5ebf674a2ce79a404ed2d003fe0f4e0726055dee4b5787f98ef28654699b1a6a4b184a7307f175a30b6ce9531f40a7d7d757335f5d1ab86dbcfbb1b77ca00ca008ef3da1570c71f443becb66522c64953cad1c9df09f5c005803794fd00929938059c019e06c8ee04d00e5096e32f43d9fbd0f153b4b9a8095c06360235c03830035c825c1fe47e86f66d6083dbcf8341c2978029600dc0bf52bc05f9b3b48ce53180823b002eb01720913dc07ae057c03c700bd80790dcb3c0bf801f022c0f03f4c83b0089166181e75053df37012fd7d1630f0057011a1e73e5f8257a02f804603c7f0390ccbd3a0141a694043a483c8ad769bcf3cf1cb4e853e018f06f80060c03d78087009643000d7d0a6800fa01167a8f84653e75db49d4f432c9d3709609806dd67f0466816cc08304e3ed77400ae06492f92e701db801d0aa4e6004380774002f00edc03618f273e8f83eda9c4fef309ee9856980db3c003c0a44811500f58e02278117818b00c9519673e8eda3d07b0575ee90ac449bd6739bf6017f003601a5104425760124510990841f0801abdd717aad0b206154d28b7b50cb83867a1df035601940877807612dda7b01ae4f9ddf03b6033af5c89401eb594bad18e0a1617c35a0eb39f79d79692179b233af40fe0b8d2f611e7f5cb849341b77f2ef7a58bb09687539331e1580278d605be645b74d7268e6123bb796329c47796f9cfd04fba5cebc79520efdde3ab9bc2b3d88815c8155396fe6b77302ffe7c6ff00610e5ffd6a8201470000000049454e44ae426082', true); + + + -- + -- Data for Name: license_label_extended_mapping; Type: TABLE DATA; Schema: public; Owner: dspace + -- + + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991138, 1, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991139, 1, 31); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991140, 2, 32); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991141, 2, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991142, 3, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991143, 3, 34); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991144, 4, 35); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991145, 4, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991146, 5, 35); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991147, 5, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991148, 6, 23); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991149, 6, 25); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991150, 6, 27); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991151, 6, 29); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991152, 6, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991153, 7, 33); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991154, 7, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991155, 7, 23); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991156, 8, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991157, 8, 33); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991158, 8, 23); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991159, 9, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991160, 9, 23); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991161, 9, 29); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991162, 9, 25); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991163, 10, 23); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991164, 10, 25); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991165, 10, 29); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991166, 10, 24); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991167, 10, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991168, 11, 29); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991169, 11, 23); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991170, 11, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991171, 11, 27); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991172, 12, 24); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991173, 12, 23); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991174, 12, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991175, 12, 29); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991176, 13, 29); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991177, 13, 23); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991178, 13, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991179, 14, 29); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991180, 14, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991181, 14, 30); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991204, 22, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991206, 24, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991207, 25, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991208, 26, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991209, 27, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991210, 28, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991211, 29, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991212, 30, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991213, 31, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991214, 32, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991215, 33, 23); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991216, 33, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991217, 34, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991218, 35, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991219, 36, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991220, 37, 29); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991221, 37, 23); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991222, 37, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991223, 38, 29); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991224, 38, 24); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991225, 38, 23); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991226, 38, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991227, 39, 27); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991228, 39, 29); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991229, 39, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991230, 39, 23); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991231, 40, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991232, 40, 25); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991233, 40, 29); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991234, 40, 23); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991235, 41, 25); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991236, 41, 29); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991237, 41, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991238, 41, 24); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991239, 41, 23); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991240, 42, 20); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991241, 42, 27); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991242, 42, 25); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991243, 42, 23); + INSERT INTO public.license_label_extended_mapping (mapping_id, license_id, label_id) VALUES (991244, 42, 29); + + + -- + -- Name: license_definition_license_id_seq; Type: SEQUENCE SET; Schema: public; Owner: dspace + -- + + PERFORM pg_catalog.setval('public.license_definition_license_id_seq', 85, true); + + + -- + -- Name: license_label_extended_mapping_mapping_id_seq; Type: SEQUENCE SET; Schema: public; Owner: dspace + -- + + PERFORM pg_catalog.setval('public.license_label_extended_mapping_mapping_id_seq', 991244, true); + + + -- + -- Name: license_label_label_id_seq; Type: SEQUENCE SET; Schema: public; Owner: dspace + -- + + PERFORM pg_catalog.setval('public.license_label_label_id_seq', 35, true); + + END IF; -- End of license_label_extended_mapping check + END IF; -- End of license_label check + END IF; -- End of license_definition check +END $$; \ No newline at end of file diff --git a/dspace-api/src/test/data/dspaceFolder/config/local.cfg b/dspace-api/src/test/data/dspaceFolder/config/local.cfg index cc9ccb26bbb2..14ff9e3a72a3 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/local.cfg +++ b/dspace-api/src/test/data/dspaceFolder/config/local.cfg @@ -219,7 +219,7 @@ featured.service.teitok.description = A web-based platform for viewing, creating ##### Shibboleth ##### -authentication-shibboleth.netid-header = SHIB-NETID +authentication-shibboleth.netid-header = SHIB-NETID,eppn,persistent-id authentication-shibboleth.email-header = SHIB-MAIL authentication-shibboleth.firstname-header = SHIB-GIVENNAME authentication-shibboleth.lastname-header = SHIB-SURNAME @@ -306,4 +306,16 @@ sync.storage.service.enabled = false signposting.enabled = true # Test configuration has only EN locale (submission-forms.xml) -webui.supported.locales = en \ No newline at end of file +webui.supported.locales = en + +# Type bind configuration for the submission form with special type-bind field +# When title is something like "Type-bind test" the type-bind field will popped up +submit.type-bind.field = dc.type,dc.identifier.citation=>dc.title + +# The configuration for the Matomo tracker must have a valid URL, as it will throw an exception if it does not. +matomo.tracker.host.url = http://localhost:8135/matomo.php + +autocomplete.custom.separator.solr-subject_ac = \\|\\|\\| +autocomplete.custom.separator.solr-title_ac = \\|\\|\\| +autocomplete.custom.allowed = solr-author_ac,solr-publisher_ac,solr-dataProvider_ac,solr-dctype_ac,solr-subject_ac,solr-handle_title_ac,json_static-iso_langs.json,solr-title_ac + diff --git a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml index e388065b68fd..738e11f7b432 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/spring/api/scripts.xml @@ -91,4 +91,9 @@ + + + + + diff --git a/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml b/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml index 81eecc680164..95c2dbb727e7 100644 --- a/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml +++ b/dspace-api/src/test/data/dspaceFolder/config/submission-forms.xml @@ -225,7 +225,7 @@ it, please enter the types and the actual numbers or codes. true - twobox + autocomplete Enter appropriate subject keywords or phrases. srsc diff --git a/dspace-api/src/test/java/org/dspace/administer/FileDownloaderIT.java b/dspace-api/src/test/java/org/dspace/administer/FileDownloaderIT.java new file mode 100644 index 000000000000..ee75fddc57e4 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/administer/FileDownloaderIT.java @@ -0,0 +1,110 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.administer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +import java.util.List; + +import org.dspace.AbstractIntegrationTestWithDatabase; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Bitstream; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.Item; +import org.dspace.content.factory.ContentServiceFactory; +import org.dspace.content.service.BitstreamService; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockserver.junit.MockServerRule; + + +public class FileDownloaderIT extends AbstractIntegrationTestWithDatabase { + + @Rule + public MockServerRule mockServerRule = new MockServerRule(this); + + private Item item; + + //Prepare a community and a collection before the test + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + context.setCurrentUser(admin); + Community community = CommunityBuilder.createCommunity(context).build(); + Collection collection = CollectionBuilder.createCollection(context, community).build(); + item = ItemBuilder.createItem(context, collection).withTitle("FileDownloaderIT Item").build(); + + mockServerRule.getClient().when(request() + .withMethod("GET") + .withPath("/test400") + ).respond( + response() + .withStatusCode(400) + .withBody("test") + ); + + mockServerRule.getClient().when(request() + .withMethod("GET") + .withPath("/test") + ).respond( + response() + .withStatusCode(200) + .withHeader("Content-Disposition", "attachment; filename=\"test.txt\"") + .withBody("test") + ); + } + + //Test that when an error occurs no bitstream is actually added to the item + @Test() + public void testDownloadFileError() throws Exception { + + + BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService(); + int oldBitCount = bitstreamService.countTotal(context); + + int port = mockServerRule.getPort(); + String[] args = new String[]{"file-downloader", "-i", item.getID().toString(), + "-u", String.format("http://localhost:%s/test400", port), "-e", "admin@email.com"}; + try { + runDSpaceScript(args); + } catch (IllegalArgumentException e) { + assertEquals(0, item.getBundles().size()); + int newBitCount = bitstreamService.countTotal(context); + assertEquals(oldBitCount, newBitCount); + return; + } + assertEquals("Not expecting to get here", 0, 1); + } + + + //Test that FileDownlaoder actually adds the bitstream to the item + @Test + public void testDownloadFile() throws Exception { + + int port = mockServerRule.getPort(); + String[] args = new String[] {"file-downloader", "-i", item.getID().toString(), + "-u", String.format("http://localhost:%s/test", port), "-e", "admin@email.com"}; + runDSpaceScript(args); + + + assertEquals(1, item.getBundles().size()); + List bs = item.getBundles().get(0).getBitstreams(); + assertEquals(1, bs.size()); + assertNotNull("Expecting name to be defined", bs.get(0).getName()); + + } + +} \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java b/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java index 3f2f46836819..6af9423a5f16 100644 --- a/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/WorkspaceItemBuilder.java @@ -250,4 +250,13 @@ public WorkspaceItemBuilder withMetadata(final String schema, final String eleme final String value) { return addMetadataValue(schema, element, qualifier, value); } + + public WorkspaceItemBuilder withShareToken(String shareToken) { + try { + workspaceItem.setShareToken(shareToken); + } catch (Exception e) { + handleException(e); + } + return this; + } } diff --git a/dspace-api/src/test/java/org/dspace/core/UtilsTest.java b/dspace-api/src/test/java/org/dspace/core/UtilsTest.java index 291561ac2536..eec5b0145954 100644 --- a/dspace-api/src/test/java/org/dspace/core/UtilsTest.java +++ b/dspace-api/src/test/java/org/dspace/core/UtilsTest.java @@ -132,4 +132,24 @@ public void testInterpolateConfigsInString() { // remove the config we added configurationService.setProperty(configName, null); } + + // Replace the last occurrence of a substring + @Test + public void testReplaceLast_SingleOccurrence() { + String input = "/login/"; + String result = Utils.replaceLast(input, "/", "replacement"); + + // Expected output: "/loginreplacement" + assertEquals("/loginreplacement", result); + } + + // No replacement when the substring is not found + @Test + public void testReplaceLast_NoMatch() { + String input = "/login"; + String result = Utils.replaceLast(input, "/", "replacement"); + + // Expected output: "/login" + assertEquals("replacementlogin", result); + } } diff --git a/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java index 57acf1f1c453..a28a5a4c7508 100644 --- a/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java +++ b/dspace-api/src/test/java/org/dspace/identifier/VersionedHandleIdentifierProviderIT.java @@ -62,9 +62,6 @@ public void setUp() throws Exception { @Override public void destroy() throws Exception { super.destroy(); - // After this test has finished running, refresh application context and - // set the expected 'default' versioned handle provider back to ensure other tests don't fail - DSpaceServicesFactory.getInstance().getServiceManager().getApplicationContext().refresh(); } private void registerProvider(Class type) { diff --git a/dspace-oai/src/main/java/org/dspace/utils/SpecialItemService.java b/dspace-oai/src/main/java/org/dspace/utils/SpecialItemService.java index d62cbc481fcb..6facd49b6c28 100644 --- a/dspace-oai/src/main/java/org/dspace/utils/SpecialItemService.java +++ b/dspace-oai/src/main/java/org/dspace/utils/SpecialItemService.java @@ -11,6 +11,10 @@ import java.io.InputStreamReader; import java.io.Reader; +import java.sql.SQLException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; import java.util.Objects; import javax.xml.parsers.DocumentBuilder; @@ -18,17 +22,27 @@ import javax.xml.parsers.ParserConfigurationException; import org.dspace.app.util.DCInput; +import org.dspace.authorize.ResourcePolicy; +import org.dspace.authorize.factory.AuthorizeServiceFactory; +import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.Bitstream; +import org.dspace.content.Bundle; import org.dspace.content.DSpaceObject; import org.dspace.content.Item; +import org.dspace.content.MetadataField; import org.dspace.content.MetadataValue; +import org.dspace.content.factory.ClarinServiceFactory; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.ItemService; +import org.dspace.content.service.MetadataFieldService; +import org.dspace.content.service.clarin.ClarinItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.handle.factory.HandleServiceFactory; import org.dspace.handle.service.HandleService; +import org.dspace.xoai.exceptions.InvalidMetadataFieldException; +import org.dspace.xoai.services.impl.DSpaceFieldResolver; import org.springframework.stereotype.Component; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -47,6 +61,7 @@ */ public class SpecialItemService { private SpecialItemService() {} + private static final String FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; /** log4j logger */ private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j .LogManager.getLogger(SpecialItemService.class); @@ -267,6 +282,158 @@ public static Node getAuthor(String mdValue) { } } + /** + * Retrieves the earliest available date for an item identified by the given identifier URI. + * This method checks for any embargo date first and then retrieves the "dc.date.available" + * metadata value as a fallback if no embargo date is found. + * + * @param identifierUri The identifier URI of the item whose available date is to be retrieved. + * @return A string representation of the earliest available date, or null if no date is found or an error occurs. + */ + public static String getAvailable(String identifierUri) { + Context context = new Context(); + // Find the metadata field for "dc.identifier.uri" + String mtdField = "dc.identifier.uri"; + MetadataField metadataField = findMetadataField(context, mtdField); + if (Objects.isNull(metadataField)) { + log.error(String.format("Metadata field for %s not found.", mtdField)); + return null; + } + + // Retrieve the item using the handle + ClarinItemService clarinItemService = ClarinServiceFactory.getInstance().getClarinItemService(); + Item item; + try { + List itemList = clarinItemService.findByHandle(context, metadataField, identifierUri); + item = itemList.isEmpty() ? null : itemList.get(0); + } catch (SQLException e) { + log.error("Error retrieving item by handle.", e); + return null; + } + if (Objects.isNull(item)) { + log.error(String.format("Item for handle %s doesn't exist!", identifierUri)); + return null; + } + + // Check if there is an embargo or get the earliest available date + Date startDate = getEmbargoDate(context, item); + if (Objects.isNull(startDate)) { + startDate = getAvailableDate(context, item); + } + return (Objects.nonNull(startDate)) ? parseDateToString(startDate) : null; + } + + /** + * Finds the metadata field corresponding to the provided string. + * + * @param context The DSpace context + * @param mtd The metadata field string + * @return The MetadataField object, or null if not found. + */ + private static MetadataField findMetadataField(Context context, String mtd) { + MetadataFieldService metadataFieldService = ContentServiceFactory.getInstance().getMetadataFieldService(); + try { + return metadataFieldService.findByString(context, mtd, '.'); + } catch (SQLException e) { + log.error(String.format("Error finding metadata field %s.", mtd), e); + return null; + } + } + + /** + * Retrieves the embargo start date for the given item bitstreams. + * If an embargo has ended, the end date is returned. + * + * @param context The DSpace context + * @param item The item whose embargo date is to be retrieved. + * @return The start or end date of the embargo, or null if no embargo exists. + */ + private static Date getEmbargoDate(Context context, Item item) { + ResourcePolicyService resPolicyService = AuthorizeServiceFactory.getInstance().getResourcePolicyService(); + Date startDate = null; + for (Bundle bundle : item.getBundles()) { + for (Bitstream bitstream : bundle.getBitstreams()) { + List resPolList; + try { + resPolList = resPolicyService.find(context, bitstream, Constants.READ); + } catch (SQLException e) { + log.error(String.format("Error during finding resource policies READ for bitstream %s", + bitstream.getID().toString())); + return null; + } + for (ResourcePolicy resPol : resPolList) { + Date date = resPol.getStartDate(); + // If the embargo has already ended, use the date of its end. + if (Objects.nonNull(date) && Objects.nonNull(resPol.getEndDate())) { + date = resPol.getEndDate(); + } + if (Objects.isNull(startDate) || (Objects.nonNull(date) && date.compareTo(startDate) > 0)) { + startDate = date; + } + } + } + } + return startDate; + } + + /** + * Retrieves the available date for the given item by checking the "dc.date.available" metadata. + * + * @param context The DSpace context + * @param item The item whose available date is to be retrieved. + * @return The available date, or null if no available date is found. + */ + private static Date getAvailableDate(Context context, Item item) { + DSpaceFieldResolver dSpaceFieldResolver = new DSpaceFieldResolver(); + List metadataValueList = item.getMetadata(); + String mtdField = "dc.date.available"; + int fieldID; + try { + fieldID = dSpaceFieldResolver.getFieldID(context, mtdField); + } catch (SQLException | InvalidMetadataFieldException e) { + log.error(String.format("Error during finding ID of metadata field %s.", mtdField)); + return null; + } + Date startDate = null; + for (MetadataValue mtd : metadataValueList) { + if (mtd.getMetadataField().getID() == fieldID) { + Date availableDate = parseStringToDate(mtd.getValue()); + if (Objects.isNull(startDate) || (Objects.nonNull(availableDate) + && availableDate.compareTo(startDate) > 0)) { + startDate = availableDate; + } + } + } + return startDate; + } + + /** + * Converts date object to string formatted in the pattern. + * + * @param date The date + * @return A string representation of the provided date + */ + private static String parseDateToString(Date date) { + SimpleDateFormat dateFormat = new SimpleDateFormat(FORMAT); + return dateFormat.format(date); + } + + /** + * Parses a date string in the format into a Date object. + * + * @param dateString date string to be parsed. + * @return A Date object representing the parsed date, or null if parsing fails. + */ + private static Date parseStringToDate(String dateString) { + SimpleDateFormat dateFormat = new SimpleDateFormat(FORMAT); + try { + return dateFormat.parse(dateString); + } catch (ParseException e) { + log.warn(String.format("Date %s cannot be parsed using the format %s.", dateString, FORMAT)); + return null; + } + } + public static boolean hasOwnMetadata(List metadataValues) { if (metadataValues.size() == 1 && metadataValues.get(0).getValue().equalsIgnoreCase("true")) { return true; diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/DSpaceResourceResolver.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/DSpaceResourceResolver.java index 9d4790b9ff47..c0e540b9576c 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/DSpaceResourceResolver.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/DSpaceResourceResolver.java @@ -26,6 +26,7 @@ import org.dspace.xoai.services.impl.resources.functions.BibtexifyFn; import org.dspace.xoai.services.impl.resources.functions.FormatFn; import org.dspace.xoai.services.impl.resources.functions.GetAuthorFn; +import org.dspace.xoai.services.impl.resources.functions.GetAvailableFn; import org.dspace.xoai.services.impl.resources.functions.GetContactFn; import org.dspace.xoai.services.impl.resources.functions.GetFundingFn; import org.dspace.xoai.services.impl.resources.functions.GetLangForCodeFn; @@ -54,7 +55,7 @@ public class DSpaceResourceResolver implements ResourceResolver { new UriToLicenseFn(), new LogMissingMsgFn(), new UriToRestrictionsFn(), new ShortestIdFn(), new GetContactFn(), new GetAuthorFn(), new GetFundingFn(), new GetLangForCodeFn(), new GetPropertyFn(), new GetSizeFn(), new GetUploadedMetadataFn(), new LogMissingFn(), - new BibtexifyFn(), new FormatFn() + new BibtexifyFn(), new FormatFn(), new GetAvailableFn() ); SaxonTransformerFactory saxonTransformerFactory = (SaxonTransformerFactory) transformerFactory; diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/functions/GetAvailableFn.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/functions/GetAvailableFn.java new file mode 100644 index 000000000000..f7843abed51c --- /dev/null +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/functions/GetAvailableFn.java @@ -0,0 +1,31 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.xoai.services.impl.resources.functions; + +import org.dspace.utils.SpecialItemService; + +/** + * The GetAvailableFn class extends the StringXSLFunction to provide a custom function + * that retrieves the availability status of an item based on its identifier. + * It uses the SpecialItemService to fetch the available information. + * This function is intended to be used in XSL transformations where the + * "getAvailable" function is called with an item's identifier as a parameter. + * + * @author Michaela Paurikova(michaela.paurikova at dataquest.sk) + */ +public class GetAvailableFn extends StringXSLFunction { + @Override + protected String getFnName() { + return "getAvailable"; + } + + @Override + protected String getStringResult(String param) { + return SpecialItemService.getAvailable(param); + } +} diff --git a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/functions/StringXSLFunction.java b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/functions/StringXSLFunction.java index 163a9eb49ca1..ed260c8b2d4a 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/functions/StringXSLFunction.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/services/impl/resources/functions/StringXSLFunction.java @@ -74,7 +74,6 @@ final public XdmValue call(XdmValue[] xdmValues) throws SaxonApiException { log.warn("Empty value in call of function of StringXslFunction type"); val = ""; } - return new XdmAtomicValue(checks(getStringResult(val))); } diff --git a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java index 1db31bcf8d47..78f4571b6216 100644 --- a/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java +++ b/dspace-oai/src/main/java/org/dspace/xoai/util/ItemUtils.java @@ -12,6 +12,7 @@ import java.io.InputStream; import java.sql.SQLException; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import com.lyncode.xoai.dataprovider.xml.xoai.Element; import com.lyncode.xoai.dataprovider.xml.xoai.Metadata; @@ -106,7 +107,8 @@ public static Element.Field createValue(String name, String value) { return e; } - private static Element createBundlesElement(Context context, Item item) throws SQLException { + private static Element createBundlesElement(Context context, Item item, AtomicBoolean restricted) + throws SQLException { Element bundles = create("bundles"); List bs; @@ -136,13 +138,24 @@ private static Element createBundlesElement(Context context, Item item) throws S // get handle of parent Item of this bitstream, if there // is one: List bn = bit.getBundles(); - if (!bn.isEmpty()) { + if (bn.size() > 0) { List bi = bn.get(0).getItems(); - if (!bi.isEmpty()) { + if (bi.size() > 0) { handle = bi.get(0).getHandle(); } } - url = baseUrl + "/bitstreams/" + bit.getID().toString() + "/download"; + if (bsName == null) { + List ext = bit.getFormat(context).getExtensions(); + bsName = "bitstream_" + sid + (ext.size() > 0 ? ext.get(0) : ""); + } + if (handle != null && baseUrl != null) { + url = baseUrl + "/bitstream/" + + handle + "/" + + sid + "/" + + URLUtils.encode(bsName); + } else { + url = URLUtils.encode(bsName); + } String cks = bit.getChecksum(); String cka = bit.getChecksumAlgorithm(); @@ -166,7 +179,17 @@ private static Element createBundlesElement(Context context, Item item) throws S bitstream.getField().add(createValue("checksumAlgorithm", cka)); bitstream.getField().add(createValue("sid", bit.getSequenceID() + "")); bitstream.getField().add(createValue("id", bit.getID().toString())); - + if (!restricted.get()) { + List clarinLicenseResourceMappingList = + clarinLicenseResourceMappingService.findByBitstreamUUID(context, bit.getID()); + for (ClarinLicenseResourceMapping clrm : clarinLicenseResourceMappingList) { + if (clrm.getLicense().getRequiredInfo() != null + && clrm.getLicense().getRequiredInfo().length() > 0) { + restricted.set( true); + break; + } + } + } } } @@ -293,100 +316,11 @@ public static Metadata retrieveMetadata(Context context, Item item) { // Now adding bitstream info //indicate restricted bitstreams -> restricted access - boolean restricted = false; + AtomicBoolean restricted = new AtomicBoolean(false); try { - Element bundles = createBundlesElement(context, item); + Element bundles = createBundlesElement(context, item, restricted); metadata.getElement().add(bundles); - List bs; - - bs = item.getBundles(); - for (Bundle b : bs) { - Element bundle = create("bundle"); - bundles.getElement().add(bundle); - bundle.getField() - .add(createValue("name", b.getName())); - - Element bitstreams = create("bitstreams"); - bundle.getElement().add(bitstreams); - List bits = b.getBitstreams(); - for (Bitstream bit : bits) { - Element bitstream = create("bitstream"); - bitstreams.getElement().add(bitstream); - String url = ""; - String bsName = bit.getName(); - String sid = String.valueOf(bit.getSequenceID()); - String baseUrl = configurationService.getProperty("oai", "bitstream.baseUrl"); - String handle = null; - // get handle of parent Item of this bitstream, if there - // is one: - List bn = bit.getBundles(); - if (bn.size() > 0) { - List bi = bn.get(0).getItems(); - if (bi.size() > 0) { - handle = bi.get(0).getHandle(); - } - } - if (bsName == null) { - List ext = bit.getFormat(context).getExtensions(); - bsName = "bitstream_" + sid - + (ext.size() > 0 ? ext.get(0) : ""); - } - if (handle != null && baseUrl != null) { - url = baseUrl + "/bitstream/" - + handle + "/" - + sid + "/" - + URLUtils.encode(bsName); - } else { - url = URLUtils.encode(bsName); - } - - String cks = bit.getChecksum(); - String cka = bit.getChecksumAlgorithm(); - String oname = bit.getSource(); - String name = bit.getName(); - String description = bit.getDescription(); - - if (name != null) { - bitstream.getField().add( - createValue("name", name)); - } - if (oname != null) { - bitstream.getField().add( - createValue("originalName", name)); - } - if (description != null) { - bitstream.getField().add( - createValue("description", description)); - } - bitstream.getField().add( - createValue("format", bit.getFormat(context) - .getMIMEType())); - bitstream.getField().add( - createValue("size", "" + bit.getSizeBytes())); - bitstream.getField().add(createValue("url", url)); - bitstream.getField().add( - createValue("checksum", cks)); - bitstream.getField().add( - createValue("checksumAlgorithm", cka)); - bitstream.getField().add( - createValue("sid", bit.getSequenceID() - + "")); - bitstream.getField().add( - createValue("id", bit.getID() + "")); - if (!restricted) { - List clarinLicenseResourceMappingList = - clarinLicenseResourceMappingService.findByBitstreamUUID(context, bit.getID()); - for (ClarinLicenseResourceMapping clrm : clarinLicenseResourceMappingList) { - if (clrm.getLicense().getRequiredInfo() != null - && clrm.getLicense().getRequiredInfo().length() > 0) { - restricted = true; - break; - } - } - } - } - } } catch (SQLException e) { log.warn(e.getMessage(), e); } @@ -398,7 +332,7 @@ public static Metadata retrieveMetadata(Context context, Item item) { other.getField().add(createValue("identifier", DSpaceItem.buildIdentifier(item.getHandle()))); other.getField().add(createValue("lastModifyDate", item.getLastModified().toString())); - if (restricted) { + if (restricted.get()) { other.getField().add(createValue("restrictedAccess", "true")); } // Because we reindex Solr, which is not done in vanilla diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinAutoRegistrationController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinAutoRegistrationController.java index de2dccb9e866..3ef39cc9794c 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinAutoRegistrationController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinAutoRegistrationController.java @@ -41,7 +41,7 @@ * * This Shibboleth Authentication process is tested in ClarinShibbolethLoginFilterIT. * - * @author Milan Majchrak (milan.majchrak at dataquest.sk) + * @author Milan Majchrak (dspace at dataquest.sk) */ @RequestMapping(value = "/api/autoregistration") @RestController @@ -89,18 +89,14 @@ public ResponseEntity sendEmail(HttpServletRequest request, HttpServletResponse String helpDeskEmail = configurationService.getProperty("lr.help.mail", ""); String helpDeskPhoneNum = configurationService.getProperty("lr.help.phone", ""); String dspaceName = configurationService.getProperty("dspace.name", ""); - String dspaceNameShort = configurationService.getProperty("dspace.name.short", ""); + String dspaceNameShort = configurationService.getProperty("dspace.shortname", ""); if (StringUtils.isEmpty(uiUrl)) { log.error("Cannot load the `dspace.ui.url` property from the cfg."); throw new RuntimeException("Cannot load the `dspace.ui.url` property from the cfg."); } - // Generate token and create ClarinVerificationToken record with the token and user email. + // Generate token String verificationToken = Utils.generateHexKey(); - clarinVerificationToken.setEmail(email); - clarinVerificationToken.setToken(verificationToken); - clarinVerificationTokenService.update(context, clarinVerificationToken); - context.commit(); // Compose the url with the verification token. The user will be redirected to the UI. String autoregistrationURL = uiUrl + "/login/autoregistration?verification-token=" + verificationToken; @@ -121,6 +117,13 @@ public ResponseEntity sendEmail(HttpServletRequest request, HttpServletResponse return null; } + // Add ClarinVerificationToken record with the token and user email to the database only if the + // email was successfully send. + clarinVerificationToken.setEmail(email); + clarinVerificationToken.setToken(verificationToken); + clarinVerificationTokenService.update(context, clarinVerificationToken); + context.commit(); + return ResponseEntity.ok().build(); } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinBitstreamImportController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinBitstreamImportController.java index 63380a756c2f..1620cb0609fc 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinBitstreamImportController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinBitstreamImportController.java @@ -137,7 +137,11 @@ public BitstreamRest importBitstreamForExistingFile(HttpServletRequest request) Boolean deleted = Boolean.parseBoolean(deletedString); //set size bytes - bitstream.setSizeBytes(bitstreamRest.getSizeBytes()); + if (Objects.nonNull(bitstreamRest.getSizeBytes())) { + bitstream.setSizeBytes(bitstreamRest.getSizeBytes()); + } else { + log.info("SizeBytes is null. Bitstream UUID: " + bitstream.getID()); + } //set checksum bitstream.setChecksum(bitstreamRest.getCheckSum().getValue()); //set checksum algorithm @@ -206,7 +210,7 @@ public BitstreamRest importBitstreamForExistingFile(HttpServletRequest request) message += " for bundle with uuid: " + bundle.getID(); } log.error(message, e); - throw new RuntimeException("message", e); + throw new RuntimeException(message, e); } return bitstreamRest; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinDiscoJuiceFeedsDownloadService.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinDiscoJuiceFeedsDownloadService.java index 7fdd9a9ade54..73b5d2b3dfef 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinDiscoJuiceFeedsDownloadService.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/ClarinDiscoJuiceFeedsDownloadService.java @@ -34,7 +34,7 @@ import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; -import org.dspace.app.rest.utils.ClarinUtils; +import org.dspace.app.rest.utils.Utils; import org.dspace.services.ConfigurationService; import org.dspace.utils.DSpace; import org.json.simple.JSONArray; @@ -237,7 +237,7 @@ private static JSONArray downloadJSON(String url) { conn.setReadTimeout(10000); // Disable SSL certificate validation if (disableSSL && conn instanceof HttpsURLConnection) { - ClarinUtils.disableCertificateValidation((HttpsURLConnection) conn); + Utils.disableCertificateValidation((HttpsURLConnection) conn); } //Caution does not follow redirects, and even if you set it to http->https is not possible Object obj = parser.parse(new InputStreamReader(conn.getInputStream())); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/DBConnectionStatisticsController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/DBConnectionStatisticsController.java new file mode 100644 index 000000000000..9472a3e12f5d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/DBConnectionStatisticsController.java @@ -0,0 +1,39 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import javax.servlet.http.HttpServletRequest; + +import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.core.Context; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * Controller for retrieving database connection statistics + * + * @author Milan Majchrak (dspace at dataquest.sk) + */ +@PreAuthorize("hasAuthority('ADMIN')") +@RequestMapping(value = "/api/dbstatistics") +@RestController +public class DBConnectionStatisticsController { + @RequestMapping(method = RequestMethod.GET) + public ResponseEntity getStatistics(HttpServletRequest request) { + + Context context = ContextUtil.obtainContext(request); + if (context == null) { + return ResponseEntity.status(500).build(); + } + // Return response entity with the statistics + return ResponseEntity.ok().body(context.getHibernateStatistics()); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/HandleConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/HandleConverter.java index dbc40a21cb60..ceb6409429a1 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/HandleConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/HandleConverter.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest.converter; +import java.util.Objects; + import org.dspace.app.rest.model.HandleRest; import org.dspace.app.rest.projection.Projection; import org.dspace.handle.Handle; @@ -35,6 +37,11 @@ public HandleRest convert(Handle modelObject, Projection projection) { handleRest.setHandle(modelObject.getHandle()); handleRest.setResourceTypeID(modelObject.getResourceTypeId()); handleRest.setUrl(modelObject.getUrl()); + if (Objects.nonNull(modelObject.getDSpaceObject())) { + handleRest.setResourceId(modelObject.getDSpaceObject().getID()); + } else { + handleRest.setResourceId(null); + } return handleRest; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java index 855a80c4a3af..5f62f7cf8ae6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/SubmissionFormConverter.java @@ -163,7 +163,9 @@ private SubmissionFormFieldRest getField(DCInput dcinput, String formName) { if (dcinput.isMetadataField()) { inputField.setSelectableMetadata(selectableMetadata); inputField.setTypeBind(dcinput.getTypeBindList()); + inputField.setTypeBindField(dcinput.getTypeBindField()); inputField.setComplexDefinition(dcinput.getComplexDefinitionJSONString()); + inputField.setAutocompleteCustom(dcinput.getAutocompleteCustom()); } if (dcinput.isRelationshipField()) { selectableRelationship = getSelectableRelationships(dcinput); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/hdlresolver/HdlResolverRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/hdlresolver/HdlResolverRestController.java index 540c3fd17243..ac2a4a1f7df6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/hdlresolver/HdlResolverRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/hdlresolver/HdlResolverRestController.java @@ -9,15 +9,18 @@ import java.text.MessageFormat; import java.util.List; +import java.util.Map; import java.util.Optional; import javax.servlet.http.HttpServletRequest; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import net.handle.hdllib.HandleException; import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.utils.ContextUtil; +import org.dspace.handle.HandlePlugin; import org.dspace.handle.hdlresolver.HdlResolverDTO; import org.dspace.handle.hdlresolver.HdlResolverService; import org.springframework.beans.factory.annotation.Autowired; @@ -166,13 +169,28 @@ public ResponseEntity listHandles(HttpServletRequest request, @PathVaria /** * Maps the handle to a correct response. + * If the metadata parameter is provided, return additional handle values. * * @param request HttpServletRequest * @param handleResolver HdlResolverDTO - Handle resolver - * @return One element list using String if found, else null String. + * @return One element list using String if found, else map of metadata if param is entered, + * else "null" in case of an error */ private String resolveToURL(HttpServletRequest request, HdlResolverDTO handleResolver) { - return mapAsJson(this.hdlResolverService.resolveToURL(ContextUtil.obtainContext(request), handleResolver)); + String url = this.hdlResolverService.resolveToURL(ContextUtil.obtainContext(request), handleResolver); + String param = request.getParameter("metadata"); + if (StringUtils.isBlank(param)) { + return mapAsJson(url); + } + String handle = handleResolver.getHandle(); + try { + Map metadata = HandlePlugin.getMapHandleValues(handle); + metadata.put("URL", url); + return mapAsJson(metadata); + } catch (HandleException e) { + log.error("Failed to resolve handle values for handle: " + handle, e); + } + return "null"; } protected String mapAsJson(final String resolvedUrl) { @@ -183,6 +201,19 @@ protected String mapAsJson(final String resolvedUrl) { return json; } + protected String mapAsJson(final Map resolvedMap) { + ObjectMapper objectMapper = new ObjectMapper(); + String json = "null"; + try { + if (resolvedMap != null && !resolvedMap.isEmpty()) { + json = objectMapper.writeValueAsString(resolvedMap); + } + } catch (JsonProcessingException e) { + log.error("Error during conversion of response!", e); + } + return json; + } + protected String mapAsJson(final List jsonList) { String json = "null"; if (jsonList != null && !jsonList.isEmpty()) { @@ -194,5 +225,4 @@ protected String mapAsJson(final List jsonList) { } return json; } - } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HandleRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HandleRest.java index 6bb989eff09d..c40c9faa7017 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HandleRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/HandleRest.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest.model; +import java.util.UUID; + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import org.dspace.app.rest.RestResourceController; @@ -29,6 +31,8 @@ public class HandleRest extends BaseObjectRest { private String url; + private UUID resourceId; + public String getHandle() { return handle; } @@ -41,6 +45,10 @@ public String getUrl() { return url; } + public UUID getResourceId() { + return resourceId; + } + public void setHandle(String handle) { this.handle = handle; } @@ -53,6 +61,11 @@ public void setUrl(String url) { this.url = url; } + public void setResourceId(UUID resourceId) { + this.resourceId = resourceId; + + } + @Override public String getCategory() { return CATEGORY; diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ShareSubmissionLinkDTO.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ShareSubmissionLinkDTO.java new file mode 100644 index 000000000000..26b9d5e0679f --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/ShareSubmissionLinkDTO.java @@ -0,0 +1,29 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.model; + +/** + * This class represents a DTO that will be used to share a submission link. It will be used to return the share link + * to the user in the UI. + * + * @author Milan Majchrak (dspace at dataquest.sk) + */ +public class ShareSubmissionLinkDTO { + + private String shareLink; + + public ShareSubmissionLinkDTO() { } + + public String getShareLink() { + return shareLink; + } + + public void setShareLink(String shareLink) { + this.shareLink = shareLink; + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormFieldRest.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormFieldRest.java index 28a67730ea64..ee49f29da876 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormFieldRest.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/model/SubmissionFormFieldRest.java @@ -92,6 +92,19 @@ public class SubmissionFormFieldRest { * ComplexDefinition transformed to the JSON string */ private String complexDefinition; + + /** + * Autocomplete custom field. Give suggestions from this specific autocomplete solr index/file. + */ + private String autocompleteCustom; + + /** + * The custom field to type bind. It is used to check that the custom type bind field is defined when + * it is defined in the configuration property `submit.type-bind.field` + */ + private String typeBindField; + + /** * Getter for {@link #selectableMetadata} * @@ -298,4 +311,20 @@ public void setComplexDefinition(String complexDefinition) { public String getComplexDefinition() { return this.complexDefinition; } + + public String getAutocompleteCustom() { + return autocompleteCustom; + } + + public void setAutocompleteCustom(String autocompleteCustom) { + this.autocompleteCustom = autocompleteCustom; + } + + public String getTypeBindField() { + return typeBindField; + } + + public void setTypeBindField(String typeBindField) { + this.typeBindField = typeBindField; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinUserMetadataRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinUserMetadataRestController.java index 84a6c9b00890..05f1c34902dd 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinUserMetadataRestController.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/ClarinUserMetadataRestController.java @@ -283,7 +283,7 @@ private void sendEmailWithDownloadLink(Context context, DSpaceObject dso, String helpDeskEmail = configurationService.getProperty("lr.help.mail", ""); String helpDeskPhoneNum = configurationService.getProperty("lr.help.phone", ""); String dspaceName = configurationService.getProperty("dspace.name", ""); - String dspaceNameShort = configurationService.getProperty("dspace.name.short", ""); + String dspaceNameShort = configurationService.getProperty("dspace.shortname", ""); if (StringUtils.isEmpty(uiUrl)) { log.error("Cannot load the `dspace.ui.url` property from the cfg."); diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataBitstreamRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataBitstreamRestRepository.java index 3eb2ee46185e..224dc5a656c7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataBitstreamRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataBitstreamRestRepository.java @@ -12,10 +12,12 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.sql.SQLException; import java.util.ArrayList; @@ -32,8 +34,8 @@ import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.compress.archivers.ArchiveException; -import org.apache.commons.compress.archivers.ArchiveInputStream; -import org.apache.commons.compress.archivers.ArchiveStreamFactory; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.dspace.app.rest.Parameter; @@ -81,6 +83,11 @@ @Component(MetadataBitstreamWrapperRest.CATEGORY + "." + MetadataBitstreamWrapperRest.NAME) public class MetadataBitstreamRestRepository extends DSpaceRestRepository { private static Logger log = org.apache.logging.log4j.LogManager.getLogger(MetadataBitstreamRestRepository.class); + private final String ARCHIVE_TYPE_ZIP = "zip"; + private final String ARCHIVE_TYPE_TAR = "tar"; + // This constant is used to limit the length of the preview content stored in the database to prevent + // the database from being overloaded with large amounts of data. + private static final int MAX_PREVIEW_COUNT_LENGTH = 2000; @Autowired HandleService handleService; @@ -170,8 +177,12 @@ public Page findByHandle(@Parameter(value = "handl // Generate new content if we didn't find any if (prContents.isEmpty()) { fileInfos = getFilePreviewContent(context, bitstream, fileInfos); - for (FileInfo fi : fileInfos) { - createPreviewContent(context, bitstream, fi); + // Do not store HTML content in the database because it could be longer than the limit + // of the database column + if (!StringUtils.equals("text/html", bitstream.getFormat(context).getMIMEType())) { + for (FileInfo fi : fileInfos) { + createPreviewContent(context, bitstream, fi); + } } } else { for (PreviewContent pc : prContents) { @@ -311,23 +322,28 @@ private List processInputStreamToFilePreview(Context context, Bitstrea List fileInfos, InputStream inputStream) throws IOException, SQLException, ParserConfigurationException, SAXException, ArchiveException { String bitstreamMimeType = bitstream.getFormat(context).getMIMEType(); - if (bitstreamMimeType.equals("text/plain") || bitstreamMimeType.equals("text/html")) { - String data = getFileContent(inputStream); + if (bitstreamMimeType.equals("text/plain")) { + String data = getFileContent(inputStream, true); + fileInfos.add(new FileInfo(data, false)); + } else if (bitstreamMimeType.equals("text/html")) { + String data = getFileContent(inputStream, false); fileInfos.add(new FileInfo(data, false)); } else { String data = ""; if (bitstream.getFormat(context).getMIMEType().equals("application/zip")) { - data = extractFile(inputStream, "zip"); + data = extractFile(inputStream, ARCHIVE_TYPE_ZIP); try { fileInfos = FileTreeViewGenerator.parse(data); } catch (Exception e) { log.error("Cannot extract file content because: {}", e.getMessage()); } } else if (bitstream.getFormat(context).getMIMEType().equals("application/x-tar")) { - ArchiveInputStream is = new ArchiveStreamFactory().createArchiveInputStream(ArchiveStreamFactory.TAR, - inputStream); - data = extractFile(is, "tar"); - fileInfos = FileTreeViewGenerator.parse(data); + data = extractFile(inputStream, ARCHIVE_TYPE_TAR); + try { + fileInfos = FileTreeViewGenerator.parse(data); + } catch (Exception e) { + log.error("Cannot extract file content because: {}", e.getMessage()); + } } } return fileInfos; @@ -370,76 +386,108 @@ private String composePreviewURL(Context context, Item item, Bitstream bitstream return url; } - /** - * Convert ZIP file into structured String. - * @param inputStream Input stream with ZIP content - * @param fileType ZIP/TAR - * @return structured String + * Creates a temporary file with the appropriate extension based on the specified file type. + * @param fileType the type of file for which to create a temporary file + * @return a Path object representing the temporary file + * @throws IOException if an I/O error occurs while creating the file */ - public String extractFile(InputStream inputStream, String fileType) { - List filePaths = new ArrayList<>(); - Path tempFile = null; - FileSystem zipFileSystem = null; + private Path createTempFile(String fileType) throws IOException { + String extension = ARCHIVE_TYPE_TAR.equals(fileType) ? + String.format(".%s", ARCHIVE_TYPE_TAR) : String.format(".%s", ARCHIVE_TYPE_ZIP); + return Files.createTempFile("temp", extension); + } - try { - switch (fileType) { - case "tar": - tempFile = Files.createTempFile("temp", ".tar"); - break; - default: - tempFile = Files.createTempFile("temp", ".zip"); + /** + * Adds a file path and its size to the list of file paths. + * If the path represents a directory, appends a "/" to the path. + * @param filePaths the list of file paths to add to + * @param path the file or directory path + * @param size the size of the file or directory + */ + private void addFilePath(List filePaths, String path, long size) { + String fileInfo = (Files.isDirectory(Paths.get(path))) ? path + "/|" + size : path + "|" + size; + filePaths.add(fileInfo); + } + /** + * Processes a TAR file, extracting its entries and adding their paths to the provided list. + * @param filePaths the list to populate with the extracted file paths + * @param tempFile the temporary TAR file to process + * @throws IOException if an I/O error occurs while reading the TAR file + */ + private void processTarFile(List filePaths, Path tempFile) throws IOException { + try (InputStream fi = Files.newInputStream(tempFile); + TarArchiveInputStream tis = new TarArchiveInputStream(fi)) { + TarArchiveEntry entry; + while ((entry = tis.getNextTarEntry()) != null) { + addFilePath(filePaths, entry.getName(), entry.getSize()); } + } + } - Files.copy(inputStream, tempFile, StandardCopyOption.REPLACE_EXISTING); + /** + * Processes a ZIP file, extracting its entries and adding their paths to the provided list. + * @param filePaths the list to populate with the extracted file paths + * @param zipFileSystem the FileSystem object representing the ZIP file + * @throws IOException if an I/O error occurs while reading the ZIP file + */ + private void processZipFile(List filePaths, FileSystem zipFileSystem) throws IOException { + Path root = zipFileSystem.getPath("/"); + Files.walk(root).forEach(path -> { + try { + long fileSize = Files.size(path); + addFilePath(filePaths, path.toString().substring(1), fileSize); + } catch (IOException e) { + log.error("An error occurred while getting the size of the zip file.", e); + } + }); + } - zipFileSystem = FileSystems.newFileSystem(tempFile, (ClassLoader) null); - Path root = zipFileSystem.getPath("/"); - Files.walk(root) - .forEach(path -> { - try { - long fileSize = Files.size(path); - if (Files.isDirectory(path)) { - filePaths.add(path.toString().substring(1) + "/|" + fileSize ); - } else { - filePaths.add(path.toString().substring(1) + "|" + fileSize ); - } - } catch (IOException e) { - e.printStackTrace(); - } - }); - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (zipFileSystem != null) { - try { - zipFileSystem.close(); - } catch (IOException e) { - e.printStackTrace(); - } + /** + * Closes the specified FileSystem resource if it is not null. + * @param zipFileSystem the FileSystem to close + */ + private void closeFileSystem(FileSystem zipFileSystem) { + if (Objects.nonNull(zipFileSystem)) { + try { + zipFileSystem.close(); + } catch (IOException e) { + log.error("An error occurred while closing the zip file.", e); } + } + } - if (tempFile != null) { - try { - Files.delete(tempFile); - } catch (IOException e) { - e.printStackTrace(); - } + /** + * Deletes the specified temporary file if it is not null. + * @param tempFile the Path object representing the temporary file to delete + */ + private void deleteTempFile(Path tempFile) { + if (Objects.nonNull(tempFile)) { + try { + Files.delete(tempFile); + } catch (IOException e) { + log.error("An error occurred while deleting temp file.", e); } } + } + /** + * Builds an XML response string based on the provided list of file paths. + * @param filePaths the list of file paths to include in the XML response + * @return an XML string representation of the file paths + */ + private String buildXmlResponse(List filePaths) { // Is a folder regex String folderRegex = "/|\\d+"; Pattern pattern = Pattern.compile(folderRegex); StringBuilder sb = new StringBuilder(); - sb.append(("")); + sb.append(""); Iterator iterator = filePaths.iterator(); int fileCounter = 0; - while ((iterator.hasNext() && fileCounter < maxPreviewCount)) { + while (iterator.hasNext() && fileCounter < maxPreviewCount) { String filePath = iterator.next(); - // Check if the file is a folder Matcher matcher = pattern.matcher(filePath); if (!matcher.matches()) { @@ -452,27 +500,90 @@ public String extractFile(InputStream inputStream, String fileType) { if (fileCounter > maxPreviewCount) { sb.append("...too many files...|0"); } - sb.append(("")); + sb.append(""); return sb.toString(); } + /** + * Extracts files from an InputStream, processes them based on the specified file type (tar or zip), + * and returns an XML representation of the file paths. + * @param inputStream the InputStream containing the file data + * @param fileType the type of file to extract ("tar" or "zip") + * @return an XML string representing the extracted file paths + */ + public String extractFile(InputStream inputStream, String fileType) { + List filePaths = new ArrayList<>(); + Path tempFile = null; + FileSystem zipFileSystem = null; + + try { + // Create a temporary file based on the file type + tempFile = createTempFile(fileType); + + // Copy the input stream to the temporary file + Files.copy(inputStream, tempFile, StandardCopyOption.REPLACE_EXISTING); + + // Process the file based on its type + if (ARCHIVE_TYPE_TAR.equals(fileType)) { + processTarFile(filePaths, tempFile); + } else { + zipFileSystem = FileSystems.newFileSystem(tempFile, (ClassLoader) null); + processZipFile(filePaths, zipFileSystem); + } + } catch (IOException e) { + log.error(String.format("An error occurred while extracting file of type %s.", fileType), e); + } finally { + closeFileSystem(zipFileSystem); + deleteTempFile(tempFile); + } + + return buildXmlResponse(filePaths); + } + /** * Read input stream and return content as String * @param inputStream to read * @return content of the inputStream as a String * @throws IOException */ - public String getFileContent(InputStream inputStream) throws IOException { + public String getFileContent(InputStream inputStream, boolean cutResult) throws IOException { StringBuilder content = new StringBuilder(); - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); - - String line; - while ((line = reader.readLine()) != null) { - content.append(line).append("\n"); + // Generate the preview content in the UTF-8 encoding + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + try { + String line; + while ((line = reader.readLine()) != null) { + content.append(line).append("\n"); + } + } catch (UnsupportedEncodingException e) { + log.error("UnsupportedEncodingException during creating the preview content because: ", e); + } catch (IOException e) { + log.error("IOException during creating the preview content because: ", e); } reader.close(); - return content.toString(); + return cutResult ? ensureMaxLength(content.toString()) : content.toString(); + } + + /** + * Trims the input string to ensure it does not exceed the maximum length for the database column. + * @param input The original string to be trimmed. + * @return A string that is truncated to the maximum length if necessary. + */ + private static String ensureMaxLength(String input) { + if (input == null) { + return null; + } + + // Check if the input string exceeds the maximum preview length + if (input.length() > MAX_PREVIEW_COUNT_LENGTH) { + // Truncate the string and append " . . ." + int previewLength = MAX_PREVIEW_COUNT_LENGTH - 6; // Subtract length of " . . ." + return input.substring(0, previewLength) + " . . ."; + } else { + // Return the input string as is if it's within the preview length + return input; + } } /** diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataValueRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataValueRestRepository.java index 7174dae9f12f..d192abb7b6c7 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataValueRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/MetadataValueRestRepository.java @@ -10,16 +10,7 @@ import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedList; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -29,6 +20,7 @@ import org.dspace.app.rest.exception.DSpaceBadRequestException; import org.dspace.app.rest.model.MetadataValueWrapper; import org.dspace.app.rest.model.MetadataValueWrapperRest; +import org.dspace.app.rest.utils.Utils; import org.dspace.content.MetadataField; import org.dspace.content.MetadataValue; import org.dspace.content.service.ItemService; @@ -51,7 +43,7 @@ /** * This is the repository responsible to manage MetadataValueWrapper Rest object. * - * @author Milan Majchrak (milan.majchrak at dataquest.sk) + * @author Milan Majchrak (dspace at dataquest.sk) */ @Component(MetadataValueWrapperRest.CATEGORY + "." + MetadataValueWrapperRest.NAME) public class MetadataValueRestRepository extends DSpaceRestRepository { @@ -122,31 +114,17 @@ public Page findByValue(@Parameter(value = "schema", r // Find matches in Solr Search core DiscoverQuery discoverQuery = - this.createDiscoverQuery(metadataField, searchValue, pageable); + this.createDiscoverQuery(metadataField, searchValue); if (ObjectUtils.isEmpty(discoverQuery)) { throw new IllegalArgumentException("Cannot create a DiscoverQuery from the arguments."); } - // regex if searchValue consist of numbers and characters - // \d - digit - String regexNumber = "(.)*(\\d)(.)*"; - // \D - non digit - String regexString = "(.)*(\\D)(.)*"; - Pattern patternNumber = Pattern.compile(regexNumber); - Pattern patternString = Pattern.compile(regexString); - // if the searchValue is mixed with numbers and characters the Solr ignore numbers by default - // divide the characters and numbers from searchValue to the separate queries and from separate queries - // create one complex query - if (patternNumber.matcher(searchValue).matches() && patternString.matcher(searchValue).matches()) { - List characterList = this.extractCharacterListFromString(searchValue); - List numberList = this.extractNumberListFromString(searchValue); - - String newQuery = this.composeQueryWithNumbersAndChars(metadataField, characterList, numberList); - discoverQuery.setQuery(newQuery); + String normalizedQuery = Utils.normalizeDiscoverQuery(searchValue, metadataField); + if (StringUtils.isNotBlank(normalizedQuery)) { + discoverQuery.setQuery(normalizedQuery); } - List metadataValueWrappers = new ArrayList<>(); try { DiscoverResult searchResult = searchService.search(context, discoverQuery); @@ -183,40 +161,6 @@ public Page findByValue(@Parameter(value = "schema", r return converter.toRestPage(metadataValueWrappers, pageable, utils.obtainProjection()); } - /** - * From searchValue get all String values which are separated by the number to the List of Strings. - * @param searchValue e.g. 'my1Search2' - * @return e.g. [my, Search] - */ - private List extractCharacterListFromString(String searchValue) { - List characterList = null; - // get characters from searchValue as List - searchValue = searchValue.replaceAll("[0-9]", " "); - characterList = new LinkedList<>(Arrays.asList(searchValue.split(" "))); - // remove empty characters from the characterList - characterList.removeIf(characters -> characters == null || "".equals(characters)); - - return characterList; - } - - /** - * From searchValue get all number values which are separated by the number to the List of Strings. - * @param searchValue e.g. 'my1Search2' - * @return e.g. [1, 2] - */ - private List extractNumberListFromString(String searchValue) { - List numberList = new ArrayList<>(); - - // get numbers from searchValue as List - Pattern numberRegex = Pattern.compile("-?\\d+"); - Matcher numberMatcher = numberRegex.matcher(searchValue); - while (numberMatcher.find()) { - numberList.add(numberMatcher.group()); - } - - return numberList; - } - public List filterEUSponsors(List metadataWrappers) { return metadataWrappers.stream().filter(m -> !m.getMetadataValue().getValue().contains("info:eu-repo")) .collect(Collectors.toList()); @@ -224,51 +168,14 @@ public List filterEUSponsors(List me public List distinctMetadataValues(List metadataWrappers) { return metadataWrappers.stream().filter( - distinctByKey(metadataValueWrapper -> metadataValueWrapper.getMetadataValue().getValue()) ) + Utils.distinctByKey(metadataValueWrapper -> metadataValueWrapper.getMetadataValue().getValue()) ) .collect( Collectors.toList() ); } - /** - * From list of String and list of Numbers create a query for the SolrQuery. - * @param metadataField e.g. `dc.contributor.author` - * @param characterList e.g. [my, Search] - * @param numberList e.g. [1, 2] - * @return "dc.contributor.author:*my* AND dc.contributor.author:*Search* AND dc.contributor.author:*1* AND ..." - */ - private String composeQueryWithNumbersAndChars(String metadataField, List characterList, - List numberList) { - this.addQueryTemplateToList(metadataField, characterList); - this.addQueryTemplateToList(metadataField, numberList); - - String joinedChars = String.join(" AND ", characterList); - String joinedNumbers = String.join(" AND ", numberList); - return joinedChars + " AND " + joinedNumbers; - - } - - /** - * Add SolrQuery template to the every item of the List - * @param metadataField e.g. `dc.contributor.author` - * @param stringList could be List of String or List of Numbers which are in the String format because of Solr - * e.g. [my, Search] - * @return [dc.contributor.author:*my*, dc.contributor.author:*Search*] - */ - private List addQueryTemplateToList(String metadataField, List stringList) { - String template = metadataField + ":" + "*" + " " + "*"; - - AtomicInteger index = new AtomicInteger(); - stringList.forEach(characters -> { - String queryString = template.replaceAll(" ", characters); - stringList.set(index.getAndIncrement(), queryString); - }); - return stringList; - } - - private DiscoverQuery createDiscoverQuery(String metadataField, String searchValue, Pageable pageable) { + private DiscoverQuery createDiscoverQuery(String metadataField, String searchValue) { DiscoverQuery discoverQuery = new DiscoverQuery(); discoverQuery.setQuery(metadataField + ":" + "*" + searchValue + "*"); - discoverQuery.setStart(Math.toIntExact(pageable.getOffset())); - discoverQuery.setMaxResults(pageable.getPageSize()); + discoverQuery.setMaxResults(500); // return only metadata field values discoverQuery.addSearchField(metadataField); discoverQuery.addFilterQueries("search.resourcetype:" + IndexableItem.TYPE); @@ -286,14 +193,6 @@ private List convertMetadataValuesToWrappers(List Predicate distinctByKey(Function keyExtractor) { - Map map = new ConcurrentHashMap<>(); - return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; - } @Override @PreAuthorize("permitAll()") diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionController.java new file mode 100644 index 000000000000..fb7c06f606e8 --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SubmissionController.java @@ -0,0 +1,223 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.List; +import java.util.Locale; +import java.util.UUID; +import javax.mail.MessagingException; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.BadRequestException; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.converter.ConverterService; +import org.dspace.app.rest.model.RestAddressableModel; +import org.dspace.app.rest.model.ShareSubmissionLinkDTO; +import org.dspace.app.rest.model.WorkspaceItemRest; +import org.dspace.app.rest.utils.Utils; +import org.dspace.authorize.AuthorizeException; +import org.dspace.authorize.service.AuthorizeService; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.core.Email; +import org.dspace.core.I18nUtil; +import org.dspace.eperson.EPerson; +import org.dspace.services.ConfigurationService; +import org.dspace.web.ContextUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * This class' purpose is to provide an API for sharing an in-progress submission. It allows the user to generate + * a share link for a workspace item and to set the owner of the workspace item to the current user. + * + * @author Milan Majchrak (dspace at dataquest.sk) + */ +@RestController +@RequestMapping("/api/" + RestAddressableModel.SUBMISSION) +public class SubmissionController { + private static Logger log = org.apache.logging.log4j.LogManager.getLogger(SubmissionController.class); + + @Autowired + WorkspaceItemService workspaceItemService; + + @Autowired + ConfigurationService configurationService; + + @Autowired + protected Utils utils; + + @Autowired + AuthorizeService authorizeService; + + @Lazy + @Autowired + protected ConverterService converter; + + @PreAuthorize("hasPermission(#wsoId, 'WORKSPACEITEM', 'WRITE')") + @RequestMapping(method = RequestMethod.GET, value = "share") + public ResponseEntity generateShareLink(@RequestParam(name = "workspaceitemid") + Integer wsoId, HttpServletRequest request) + throws SQLException, AuthorizeException { + + Context context = ContextUtil.obtainContext(request); + // Check the context is not null + this.validateContext(context); + + // Get workspace item from ID + WorkspaceItem wsi = workspaceItemService.find(context, wsoId); + // Check the wsi does exist + validateWorkspaceItem(wsi, wsoId, null); + + // Generate a share link + String shareToken = generateShareToken(); + + // Update workspace item with share link + wsi.setShareToken(shareToken); + workspaceItemService.update(context, wsi); + // Without commit the changes are not persisted into the database + context.commit(); + + // Get submitter email + EPerson currentUser = context.getCurrentUser(); + if (currentUser == null) { + String errorMessage = "The current user is not valid, it cannot be null."; + log.error(errorMessage); + throw new BadRequestException(errorMessage); + } + + // Send email to submitter with share link + String shareLink = sendShareLinkEmail(context, wsi, currentUser); + if (StringUtils.isEmpty(shareLink)) { + String errorMessage = "The share link is empty."; + log.error(errorMessage); + throw new RuntimeException(errorMessage); + } + + // Create a DTO with the share link for better processing in the FE + ShareSubmissionLinkDTO shareSubmissionLinkDTO = new ShareSubmissionLinkDTO(); + shareSubmissionLinkDTO.setShareLink(shareLink); + + // Send share link in response + return ResponseEntity.ok().body(shareSubmissionLinkDTO); + } + + @PreAuthorize("hasPermission(#wsoId, 'WORKSPACEITEM', 'WRITE')") + @RequestMapping(method = RequestMethod.GET, value = "setOwner") + public WorkspaceItemRest setOwner(@RequestParam(name = "shareToken") String shareToken, + @RequestParam(name = "workspaceitemid") Integer wsoId, + HttpServletRequest request) + throws SQLException, AuthorizeException { + + Context context = ContextUtil.obtainContext(request); + // Check the context is not null + this.validateContext(context); + + // Get workspace by share token + List wsiList = workspaceItemService.findByShareToken(context, shareToken); + // Check the wsi does exist + if (CollectionUtils.isEmpty(wsiList)) { + String errorMessage = "The workspace item with share token:" + shareToken + " does not exist."; + log.error(errorMessage); + throw new BadRequestException(errorMessage); + } + + // Get the first workspace item - the only one + WorkspaceItem wsi = wsiList.get(0); + // Check the wsi does exist + validateWorkspaceItem(wsi, null, shareToken); + + if (!authorizeService.authorizeActionBoolean(context, wsi.getItem(), Constants.READ)) { + String errorMessage = "The current user does not have rights to view the WorkflowItem"; + log.error(errorMessage); + throw new AccessDeniedException(errorMessage); + } + + // Set the owner of the workspace item to the current user + EPerson currentUser = context.getCurrentUser(); + // If the current user is null, throw an exception + if (currentUser == null) { + String errorMessage = "The current user is not valid, it cannot be null."; + log.error(errorMessage); + throw new BadRequestException(errorMessage); + } + + wsi.getItem().setSubmitter(currentUser); + workspaceItemService.update(context, wsi); + WorkspaceItemRest wsiRest = converter.toRest(wsi, utils.obtainProjection()); + + // Without commit the changes are not persisted into the database + context.commit(); + return wsiRest; + } + + private static String generateShareToken() { + // UUID generates a 36-char string with hyphens, so we can strip them to get a 32-char string + return UUID.randomUUID().toString().replace("-", "").substring(0, 32); + } + + private String sendShareLinkEmail(Context context, WorkspaceItem wsi, EPerson currentUser) { + // Get the UI URL from the configuration + String uiUrl = configurationService.getProperty("dspace.ui.url"); + // Get submitter email + String email = currentUser.getEmail(); + // Compose the url with the share token. The user will be redirected to the UI. + String shareTokenUrl = uiUrl + "/share-submission/change-submitter?share_token=" + wsi.getShareToken() + + "&workspaceitemid=" + wsi.getID(); + try { + Locale locale = context.getCurrentLocale(); + Email bean = Email.getEmail(I18nUtil.getEmailFilename(locale, "share_submission")); + bean.addArgument(shareTokenUrl); + bean.addRecipient(email); + bean.send(); + } catch (MessagingException | IOException e) { + String errorMessage = "Unable send the email because: " + e.getMessage(); + log.error(errorMessage); + throw new RuntimeException(errorMessage); + } + return shareTokenUrl; + } + + /** + * Check if the context is valid - not null. If not, throw an exception. + */ + private void validateContext(Context context) { + if (context == null) { + String errorMessage = "The current context is not valid, it cannot be null."; + log.error(errorMessage); + throw new RuntimeException(errorMessage); + } + } + + /** + * Check if the workspace item is valid - not null. If not, throw an exception. The workspace item can be found by + * ID or the share token. + */ + private void validateWorkspaceItem(WorkspaceItem wsi, Integer wsoId, String shareToken) { + if (wsi == null) { + String identifier = wsoId != null ? wsoId.toString() : shareToken; + String identifierName = wsoId != null ? "ID" : "share token"; + String errorMessage = "The workspace item with " + identifierName + ":" + identifier + " does not exist."; + log.error(errorMessage); + throw new BadRequestException(errorMessage); + } + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionRestController.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionRestController.java new file mode 100644 index 000000000000..b0f5c051979d --- /dev/null +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/SuggestionRestController.java @@ -0,0 +1,400 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.repository; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.BadRequestException; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.dspace.app.rest.model.VocabularyEntryRest; +import org.dspace.app.rest.model.hateoas.VocabularyEntryResource; +import org.dspace.app.rest.utils.Utils; +import org.dspace.core.Context; +import org.dspace.discovery.DiscoverQuery; +import org.dspace.discovery.DiscoverResult; +import org.dspace.discovery.SearchService; +import org.dspace.discovery.SearchServiceException; +import org.dspace.discovery.indexobject.IndexableItem; +import org.dspace.services.ConfigurationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.hateoas.PagedModel; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * Returns VocabularyEntries that contain searchValue. The search is performed on the specific index that is defined by + * the `autocompleteCustom` parameter in the `submission-forms.xml`. + * + * @author Milan Majchrak (dspace at dataquest.sk) + */ +@RestController +@RequestMapping("/api/suggestions") +public class SuggestionRestController extends AbstractDSpaceRestRepository { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SuggestionRestController.class); + + + /** + * Prefix for the configuration that defines the separator for the autocompleteCustom parameter. + */ + private static final String AUTOCOMPLETE_CUSTOM_CFG_FORMAT_PREFIX = "autocomplete.custom.separator."; + + /** + * Solr prefix for the autocompleteCustom parameter that define the source of the suggestions. + */ + private static final String AUTOCOMPLETE_CUSTOM_SOLR_PREFIX = "solr-"; + + /** + * Json file prefix for the autocompleteCustom parameter that define the source of the suggestions. + */ + private static final String AUTOCOMPLETE_CUSTOM_JSON_PREFIX = "json_static-"; + + /** + * Query parameter from the autocompleteCustom parameter that define specific query for the Solr search. + */ + private static final String AUTOCOMPLETE_CUSTOM_SOLR_QUERY_PARAM = "query="; + + /** + * Limit of suggestions that will be returned from the JSON file. The limit is used to prevent + * the loading of a large amount of data from the JSON file. + */ + private static final int JSON_SUGGESTIONS_LIMIT = 8; + + @Autowired + private SearchService searchService; + + @Autowired + private ConfigurationService configurationService; + + /** + * Map that contains loaded JSON suggestions. The key is the autocompleteCustom parameter and the value is the + * loaded JSON data. The JSON data is loaded only once and stored in the map for further use. + */ + Map jsonSuggestions = new HashMap<>(); + + /** + * Returns a list of VocabularyEntryRest objects that contain values that contain searchValue. + * The search is performed on the specific index or a specific json file that is defined + * by the autocompleteCustom parameter. + */ + @PreAuthorize("hasAuthority('AUTHENTICATED')") + @RequestMapping(method = RequestMethod.GET) + public PagedModel filter(@Nullable HttpServletRequest request, + @Nullable Pageable optionalPageable, + @RequestParam(name = "autocompleteCustom", required = false) + String autocompleteCustom, + @RequestParam(name = "searchValue", required = false) + String searchValue, + PagedResourcesAssembler assembler) throws SearchServiceException { + // If the searching for the autocompleteCustom parameter is not allowed, return an error + if (!isAllowedSearching(autocompleteCustom)) { + String errorMessage = "Searching for autocompleteCustom: " + autocompleteCustom + " is not allowed"; + log.warn(errorMessage); + throw new BadRequestException(errorMessage); + } + + Pageable pageable = utils.getPageable(optionalPageable); + List results; + // Load suggestions from the specific source (Solr or JSON) + if (autocompleteCustom.startsWith(AUTOCOMPLETE_CUSTOM_JSON_PREFIX)) { + results = getSuggestions(autocompleteCustom, searchValue, AUTOCOMPLETE_CUSTOM_JSON_PREFIX); + } else if (autocompleteCustom.startsWith(AUTOCOMPLETE_CUSTOM_SOLR_PREFIX)) { + results = getSuggestions(autocompleteCustom, searchValue, AUTOCOMPLETE_CUSTOM_SOLR_PREFIX); + } else { + log.warn("Cannot fetch suggestions for autocompleteCustom: {} with searching value: {}", + autocompleteCustom, searchValue); + // Return empty list + results = new ArrayList<>(0); + } + + // If no results are found, return null + if (CollectionUtils.isEmpty(results)) { + log.info("No suggestions found for autocompleteCustom: {} with searching value: {}", + autocompleteCustom, searchValue); + } + + // Remove duplicates from the results + List finalResults = results.stream() + .filter(Utils.distinctByKey(VocabularyEntryRest::getValue)) + .collect(Collectors.toList()); + + // Remove `?query` from the autocompleteCustom parameter if it contains this specific query parameter + String autocompleteCustomWithoutQuery = updateAutocompleteAndQuery(autocompleteCustom, null); + // Format the values according to the configuration + finalResults = finalResults.stream() + .map(ver -> formatValue(ver, autocompleteCustomWithoutQuery)) + .collect(Collectors.toList()); + + // Create a page with the final results. The page is needed for the better processing in the frontend. + Page resultsPage = new PageImpl<>(finalResults, pageable, finalResults.size()); + PagedModel response = assembler.toModel(resultsPage); + return response; + } + + /** + * Returns a list of VocabularyEntryRest objects which contain values with searching value. + * The search is performed on the specific index or json file that is defined by the autocompleteCustom parameter. + */ + private List getSuggestions(String autocompleteCustom, String searchValue, String prefix) + throws SearchServiceException { + // Remove the prefix from the autocompleteCustom parameter + String normalizedAutocompleteCustom = removeAutocompleteCustomPrefix(prefix, autocompleteCustom); + // Normalize the search value - remove leading and trailing whitespaces + String normalizedSearchValue = searchValue.trim(); + // Create a list of VocabularyEntryRest objects that will be filtered from duplicate values and returned + // as a response. + List results = new ArrayList<>(); + + if (prefix.equals(AUTOCOMPLETE_CUSTOM_SOLR_PREFIX)) { + // Load suggestions from Solr + results = loadSuggestionsFromSolr(normalizedAutocompleteCustom, normalizedSearchValue, results); + } else if (prefix.equals(AUTOCOMPLETE_CUSTOM_JSON_PREFIX)) { + // Load suggestions from JSON + results = loadSuggestionsFromJson(normalizedAutocompleteCustom, normalizedSearchValue, results); + } + + return results; + + } + + /** + * Load suggestions from the JSON file. The JSON file is loaded only once and stored in the map for further use. + * The search is performed on the specific key in the JSON file. The key is the autocompleteCustom parameter. + */ + private List loadSuggestionsFromJson(String autocompleteCustom, String searchValue, + List results) { + try { + // Load the JSON data from the file. + JsonNode jsonData; + if (!jsonSuggestions.containsKey(autocompleteCustom)) { + // Load the JSON data from the file and store it in the map for further use. + JsonNode loadedJsonSuggestions = loadJsonFromFile(autocompleteCustom); + jsonData = loadedJsonSuggestions; + jsonSuggestions.put(autocompleteCustom, loadedJsonSuggestions); + } else { + // Get the JSON data from the map + jsonData = jsonSuggestions.get(autocompleteCustom); + } + + if (jsonData == null) { + log.warn("Cannot load JSON suggestions from file: {}", autocompleteCustom); + return results; + } + + // Search for a specific key + results = searchByKey(jsonData, searchValue, results); + + } catch (IOException e) { + log.error("Error while loading JSON suggestions from file: {} because: {}", autocompleteCustom, + e.getMessage()); + } + return results; + } + + /** + * Load suggestions from Solr. The search is performed on the specific index that is defined by the + * autocompleteCustom parameter. + */ + private List loadSuggestionsFromSolr(String autocompleteCustom, String searchValue, + List results) + throws SearchServiceException { + Context context = obtainContext(); + // Create a DiscoverQuery object that will be used to search for the results. + DiscoverQuery discoverQuery = new DiscoverQuery(); + // Process the custom query if it contains the specific query parameter `?query=` + autocompleteCustom = updateAutocompleteAndQuery(autocompleteCustom, discoverQuery); + // TODO - search facets and process facet results instead of indexable objects + discoverQuery.setMaxResults(500); + // return only metadata field values + discoverQuery.addSearchField(autocompleteCustom); + + String normalizedQuery = Utils.normalizeDiscoverQuery(searchValue, autocompleteCustom); + if (StringUtils.isNotBlank(normalizedQuery)) { + discoverQuery.setQuery(normalizedQuery); + } + + // Search for the results + DiscoverResult searchResult = searchService.search(context, discoverQuery); + + // Iterate over all indexable objects in the search result. We need indexable object to get search documents. + // Each search document contains values from the specific index. + processSolrSearchResults(searchResult, autocompleteCustom, searchValue, results); + + return results; + } + + /** + * Process the search results from Solr. The search results are processed and filtered according to the searchValue. + */ + private void processSolrSearchResults(DiscoverResult searchResult, String autocompleteCustom, String searchValue, + List results) { + searchResult.getIndexableObjects().forEach(object -> { + if (!(object instanceof IndexableItem)) { + return; + } + IndexableItem item = (IndexableItem) object; + // Get all search documents for the item. + searchResult.getSearchDocument(item).forEach((searchDocument) -> { + VocabularyEntryRest vocabularyEntryRest = new VocabularyEntryRest(); + // All values from Item's specific index - it could contain values we are not looking for. + // The must be filtered out. + List docValues = searchDocument.getSearchFieldValues(autocompleteCustom); + + // Filter values that contain searchValue + List filteredValues = docValues.stream() + .filter(value -> value.contains(searchValue)) + .collect(Collectors.toList()); + + // Add filtered values to the results. It contains only values that contain searchValue. + filteredValues.forEach(value -> { + vocabularyEntryRest.setDisplay(value); + vocabularyEntryRest.setValue(value); + results.add(vocabularyEntryRest); + }); + }); + }); + } + + /** + * Process the custom query if it contains the specific query parameter `?query=`. + * The query is processed and set to the DiscoverQuery object. + * The method returns the part before the query parameter as the new autocompleteCustom parameter. + * @param discoverQuery could be null + */ + private String updateAutocompleteAndQuery(String autocompleteCustom, DiscoverQuery discoverQuery) { + if (!autocompleteCustom.contains(AUTOCOMPLETE_CUSTOM_SOLR_QUERY_PARAM)) { + return autocompleteCustom; + } + + // Query parameter starts with `?` + String[] parts = autocompleteCustom.split("\\?" + AUTOCOMPLETE_CUSTOM_SOLR_QUERY_PARAM); + // 2 parts are expected - the part before the query parameter and after the query parameter + if (parts.length == 2) { + if (discoverQuery != null) { + discoverQuery.setQuery(parts[1]); + } + return parts[0]; // Return the part before "?query=" + } + + return autocompleteCustom; + } + + /** + * Load JSON data from the file. The JSON data is loaded from the resources' folder. + */ + public JsonNode loadJsonFromFile(String filePath) throws IOException { + // Load the file from the resources folder + ClassPathResource resource = new ClassPathResource(filePath); + + // Use Jackson ObjectMapper to read the JSON file + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readTree(resource.getInputStream()); + } + + /** + * Search for the specific key in the JSON object. The search is performed on the specific key in the JSON object. + * The key is the autocompleteCustom parameter. + */ + public List searchByKey(JsonNode jsonNode, String searchKey, + List results) { + // Iterate over all fields (keys) in the JSON object + Iterator fieldNames = jsonNode.fieldNames(); + while (fieldNames.hasNext() && results.size() < JSON_SUGGESTIONS_LIMIT) { + String key = fieldNames.next(); + + // If the key matches or contains the search term (case-insensitive) + if (key.toLowerCase().contains(searchKey.toLowerCase())) { + // Add key-value pair to the result + VocabularyEntryRest vocabularyEntryRest = new VocabularyEntryRest(); + vocabularyEntryRest.setDisplay(key); + vocabularyEntryRest.setValue(jsonNode.get(key).asText()); + results.add(vocabularyEntryRest); + } + } + return results; + } + + /** + * Format the value according to the configuration. + * The result value could consist of multiple parts separated by a separator. Keep the correct part separated by + * the separator loaded from the configuration. + */ + private VocabularyEntryRest formatValue(VocabularyEntryRest ver, String autocompleteCustom) { + if (StringUtils.isEmpty(ver.getValue()) || StringUtils.isEmpty(autocompleteCustom)) { + return ver; + } + + // Load separator from the configuration `autocomplete.custom.separator. + String separator = configurationService.getProperty(AUTOCOMPLETE_CUSTOM_CFG_FORMAT_PREFIX + autocompleteCustom); + if (StringUtils.isEmpty(separator)) { + return ver; + } + + // Split the value by the separator and keep the correct - second part + String[] parts = ver.getValue().split(separator); + // Check the length of the parts - the correct value is the second part + if (parts.length > 1) { + String formattedValue = parts[1].trim(); // The correct value is the second part + ver.setValue(formattedValue); + ver.setDisplay(formattedValue); + } + + return ver; + } + + /** + * Remove the prefix from the autocompleteCustom parameter. E.g. remove "solr-" or "json_static-". + */ + private String removeAutocompleteCustomPrefix(String prefix, String autocompleteCustom) { + return autocompleteCustom.replace(prefix, ""); + } + + /** + * Check if the autocompleteCustom parameter is allowed to be searched. + * To allow searching, the `autocomplete.custom.allowed` property must be defined in the configuration. + */ + private boolean isAllowedSearching(String autocompleteCustom) { + // Check if the autocompleteCustom parameter is allowed to be searched + String[] allowedAutocompleteCustom = configurationService.getArrayProperty("autocomplete.custom.allowed", + new String[0]); + + // Remove `?query` from the autocompleteCustom parameter if it contains this specific query parameter + String normalizedAutocompleteCustom = updateAutocompleteAndQuery(autocompleteCustom, null); + + // If the allowedAutocompleteCustom parameter is not defined, return false + if (Objects.isNull(allowedAutocompleteCustom)) { + return false; + } + + // Convert the allowedAutocompleteCustom array to a list + List allowedAutocompleteCustomList = Arrays.asList(allowedAutocompleteCustom); + return allowedAutocompleteCustomList.contains(normalizedAutocompleteCustom); + } +} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java index d3fb87b0e501..e6ff59f2a729 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/repository/WorkspaceItemRestRepository.java @@ -22,6 +22,7 @@ import java.util.Objects; import java.util.UUID; import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.BadRequestException; import com.fasterxml.jackson.databind.JsonNode; import org.apache.commons.io.FileUtils; @@ -84,6 +85,7 @@ import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.web.multipart.MultipartFile; @@ -100,6 +102,8 @@ public class WorkspaceItemRestRepository extends DSpaceRestRepository findByShareToken(@Parameter(value = SHARE_TOKEN, required = true) String shareToken, + Pageable pageable) { + try { + Context context = obtainContext(); + List witems = workspaceItemService.findByShareToken(context, shareToken); + if (CollectionUtils.isEmpty(witems)) { + String errorMessage = "The workspace item with share token:" + shareToken + " does not exist."; + log.error(errorMessage); + throw new BadRequestException(errorMessage); + } + if (!authorizeService.authorizeActionBoolean(context, witems.get(0).getItem(), Constants.READ)) { + String errorMessage = "The current user does not have rights to view the WorkflowItem"; + log.error(errorMessage); + throw new AccessDeniedException(errorMessage); + } + // It must return a Page because the FE cannot parse single result from the search endpoint because + // the FE service always expects a Page object. + return converter.toRestPage(witems, pageable, utils.obtainProjection()); + } catch (SQLException e) { + String errorMessage = "Cannot retrieve a workspace item with the share token: " + shareToken + + " because: " + e.getMessage(); + log.error(errorMessage, e); + throw new RuntimeException(errorMessage, e); + } + } + /** * Detach the clarin license from the bitstreams and if the clarin license is not null attach the * new clarin license to the bitstream. diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java index c0efbd60f204..4f92b0f650a6 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/WorkspaceItemRestPermissionEvaluatorPlugin.java @@ -7,8 +7,12 @@ */ package org.dspace.app.rest.security; +import static org.dspace.app.rest.repository.WorkspaceItemRestRepository.SHARE_TOKEN; + import java.io.Serializable; import java.sql.SQLException; +import java.util.Objects; +import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.model.WorkspaceItemRest; @@ -91,6 +95,16 @@ public boolean hasDSpacePermission(Authentication authentication, Serializable t } } + // Check the request has shareToken the same as the workspace item + if (witem.getShareToken() != null) { + HttpServletRequest req = request.getHttpServletRequest(); + if (Objects.nonNull(req)) { + if (witem.getShareToken().equals(req.getParameter(SHARE_TOKEN))) { + return true; + } + } + } + if (witem.getItem() != null) { if (supervisionOrderService.isSupervisor(context, ePerson, witem.getItem())) { return authorizeService.authorizeActionBoolean(context, ePerson, witem.getItem(), diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/clarin/ClarinShibbolethLoginFilter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/clarin/ClarinShibbolethLoginFilter.java index 02ecaa593a90..053ddd6915ee 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/clarin/ClarinShibbolethLoginFilter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/security/clarin/ClarinShibbolethLoginFilter.java @@ -126,7 +126,6 @@ public Authentication attemptAuthentication(HttpServletRequest req, // If the Idp doesn't send the email in the request header, send the redirect order to the FE for the user // to fill in the email. - String netidHeader = configurationService.getProperty("authentication-shibboleth.netid-header"); String emailHeader = configurationService.getProperty("authentication-shibboleth.email-header"); Context context = ContextUtil.obtainContext(req); @@ -154,34 +153,34 @@ public Authentication attemptAuthentication(HttpServletRequest req, shib_headers = new ShibHeaders(req); } - // Retrieve the netid and email values from the header. - String netid = shib_headers.get_single(netidHeader); String idp = shib_headers.get_idp(); // If the clarin verification object is not null load the email from there otherwise from header. - String email = Objects.isNull(clarinVerificationToken) ? - shib_headers.get_single(emailHeader) : clarinVerificationToken.getEmail(); + String email; + if (Objects.isNull(clarinVerificationToken)) { + email = shib_headers.get_single(emailHeader); + if (StringUtils.isNotEmpty(email)) { + email = ClarinShibAuthentication.sortEmailsAndGetFirst(email); + } + } else { + email = clarinVerificationToken.getEmail(); + } EPerson ePerson = null; - if (StringUtils.isNotEmpty(netid)) { + try { + ePerson = ClarinShibAuthentication.findEpersonByNetId(shib_headers.getNetIdHeaders(), shib_headers, + ePersonService, context, false); + } catch (SQLException e) { + // It is logged in the ClarinShibAuthentication class. + } + + if (Objects.isNull(ePerson) && StringUtils.isNotEmpty(email)) { try { - // If email is null and netid exist try to find the eperson by netid and load its email - if (StringUtils.isEmpty(email)) { - ePerson = ePersonService.findByNetid(context, netid); - email = Objects.isNull(email) ? this.getEpersonEmail(ePerson) : null; - } else { - // Try to get user by the email because of possible duplicate of the user email - ePerson = ePersonService.findByEmail(context, email); - } - } catch (SQLException ignored) { - // + ePerson = ePersonService.findByEmail(context, email); + } catch (SQLException e) { + // It is logged in the ClarinShibAuthentication class. } } - // logging - log.info("Shib-Identity-Provider: " + idp); - log.info("authentication-shibboleth.netid-header: " + netidHeader + " with value: " + netid); - log.info("authentication-shibboleth.email-header: " + emailHeader + " with value: " + email); - try { if (StringUtils.isEmpty(idp)) { log.error("Cannot load the idp from the request headers."); @@ -262,6 +261,7 @@ protected void unsuccessfulAuthentication(HttpServletRequest request, String missingHeadersUrl = "missing-headers"; String userWithoutEmailUrl = "auth-failed"; String duplicateUser = "duplicate-user"; + String cannotAuthenticate = "shibboleth-authentication-failed"; // Compose the redirect URL if (this.isMissingHeadersFromIdp) { @@ -271,6 +271,11 @@ protected void unsuccessfulAuthentication(HttpServletRequest request, } else if (StringUtils.isNotEmpty(this.netId)) { // netId is set if the user doesn't have the email redirectUrl += userWithoutEmailUrl + "?netid=" + this.netId; + } else { + // Remove the last slash from the URL `login/` + String redirectUrlWithoutSlash = redirectUrl.endsWith("/") ? + Utils.replaceLast(redirectUrl, "/", "") : redirectUrl; + redirectUrl = redirectUrlWithoutSlash + "?error=" + cannotAuthenticate; } response.sendRedirect(redirectUrl); @@ -307,7 +312,12 @@ private void redirectAfterSuccess(HttpServletRequest request, HttpServletRespons if (StringUtils.equalsAnyIgnoreCase(redirectHostName, allowedHostNames.toArray(new String[0]))) { log.debug("Shibboleth redirecting to " + redirectUrl); - response.sendRedirect(redirectUrl); + // Encode the UTF-8 characters from redirect URL to UTF-8, to ensure it's properly encoded for the browser + String encodedRedirectUrl = org.dspace.app.rest.utils.Utils.encodeNonAsciiCharacters(redirectUrl); + if (StringUtils.isEmpty(encodedRedirectUrl)) { + log.error("Invalid Encoded Shibboleth redirectURL=" + redirectUrl + ". URL is empty!"); + } + response.sendRedirect(encodedRedirectUrl); } else { log.error("Invalid Shibboleth redirectURL=" + redirectUrl + ". URL doesn't match hostname of server or UI!"); @@ -340,23 +350,21 @@ protected void setMissingUserEmail(HttpServletRequest req, Context context = ContextUtil.obtainContext(req); String authenticateHeaderValue = restAuthenticationService.getWwwAuthenticateHeaderValue(req, res); - // Load header keys from cfg - String netidHeader = configurationService.getProperty("authentication-shibboleth.netid-header"); - // Store the header which the Idp has sent to the ShibHeaders object and save that header into the table // `verification_token` because after successful authentication the Idp headers will be showed for the user in // the another page. // Store header values in the ShibHeaders because of String issues. ShibHeaders shib_headers = new ShibHeaders(req); - String netid = shib_headers.get_single(netidHeader); + String[] netIdHeaders = shib_headers.getNetIdHeaders(); + String netId = getNetIdFromShibHeaders(netIdHeaders, shib_headers); // Store the Idp headers associated with the current netid. + ClarinVerificationToken clarinVerificationToken; try { - ClarinVerificationToken clarinVerificationToken = - clarinVerificationTokenService.findByNetID(context, netid); + clarinVerificationToken = clarinVerificationTokenService.findByNetID(context, netId); if (Objects.isNull(clarinVerificationToken)) { clarinVerificationToken = clarinVerificationTokenService.create(context); - clarinVerificationToken.setePersonNetID(netid); + clarinVerificationToken.setePersonNetID(netId); } clarinVerificationToken.setShibHeaders(shib_headers.toString()); clarinVerificationTokenService.update(context, clarinVerificationToken); @@ -366,7 +374,7 @@ protected void setMissingUserEmail(HttpServletRequest req, + e.getMessage()); } - this.netId = netid; + this.netId = netId; } private String getEpersonEmail(EPerson ePerson) { @@ -375,5 +383,20 @@ private String getEpersonEmail(EPerson ePerson) { } return ePerson.getEmail(); } + + /** + * Get the netId from the ShibHeaders object. The netId is stored in the headers which are defined in the + * `authentication-shibboleth.netid-headers` property. + * @return netId or null + */ + private String getNetIdFromShibHeaders(String[] netIdHeaders, ShibHeaders shibHeaders) { + for (String netidHeader : netIdHeaders) { + String netID = shibHeaders.get_single(netidHeader); + if (StringUtils.isNotEmpty(netID)) { + return netID; + } + } + return null; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DescribeStep.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DescribeStep.java index 8b0054e71775..682983acc2ab 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DescribeStep.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/DescribeStep.java @@ -63,6 +63,8 @@ */ public class DescribeStep extends AbstractProcessingStep { + public static final String KEY_VALUE_SEPARATOR = "=>"; + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(DescribeStep.class); // Input reader for form configuration @@ -93,15 +95,34 @@ public DataDescribe getData(SubmissionService submissionService, InProgressSubmi private void readField(InProgressSubmission obj, SubmissionStepConfig config, DataDescribe data, DCInputSet inputConfig) throws DCInputsReaderException { - String documentTypeValue = ""; - List documentType = itemService.getMetadataByMetadataString(obj.getItem(), - configurationService.getProperty("submit.type-bind.field", "dc.type")); - if (documentType.size() > 0) { - documentTypeValue = documentType.get(0).getValue(); + List documentTypeValueList = new ArrayList<>(); + List typeBindFields = Arrays.asList( + configurationService.getArrayProperty("submit.type-bind.field", new String[0])); + + for (String typeBindField : typeBindFields) { + String typeBFKey = typeBindField; + if (typeBindField.contains(KEY_VALUE_SEPARATOR)) { + String[] parts = typeBindField.split(KEY_VALUE_SEPARATOR); + // Get the second part of the split - the metadata field + typeBFKey = parts[1]; + } + List documentType = itemService.getMetadataByMetadataString(obj.getItem(), typeBFKey); + if (documentType.size() > 0) { + documentTypeValueList.add(documentType.get(0).getValue()); + } } // Get list of all field names (including qualdrop names) allowed for this dc.type - List allowedFieldNames = inputConfig.populateAllowedFieldNames(documentTypeValue); + List allowedFieldNames = new ArrayList<>(); + + if (CollectionUtils.isEmpty(documentTypeValueList)) { + // If no dc.type is set, we allow all fields + allowedFieldNames.addAll(inputConfig.populateAllowedFieldNames(null)); + } else { + documentTypeValueList.forEach(documentTypeValue -> { + allowedFieldNames.addAll(inputConfig.populateAllowedFieldNames(documentTypeValue)); + }); + } // Loop input rows and process submitted metadata for (DCInput[] row : inputConfig.getFields()) { diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/MetadataValidation.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/MetadataValidation.java index 1ff9639906ac..fe46ee0ba9b3 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/MetadataValidation.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/submit/step/validation/MetadataValidation.java @@ -7,6 +7,8 @@ */ package org.dspace.app.rest.submit.step.validation; +import static org.dspace.app.rest.submit.step.DescribeStep.KEY_VALUE_SEPARATOR; + import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; @@ -68,22 +70,46 @@ public List validate(SubmissionService submissionService, InProgressS SubmissionStepConfig config) throws DCInputsReaderException, SQLException { List errors = new ArrayList<>(); - String documentTypeValue = ""; DCInputSet inputConfig = getInputReader().getInputsByFormName(config.getId()); - List documentType = itemService.getMetadataByMetadataString(obj.getItem(), - configurationService.getProperty("submit.type-bind.field", "dc.type")); - if (documentType.size() > 0) { - documentTypeValue = documentType.get(0).getValue(); + List documentTypeValueList = new ArrayList<>(); + // Get the list of type-bind fields. It could be in the form of schema.element.qualifier=>metadata_field, or + // just metadata_field + List typeBindFields = Arrays.asList( + configurationService.getArrayProperty("submit.type-bind.field", new String[0])); + + for (String typeBindField : typeBindFields) { + String typeBFKey = typeBindField; + // If the type-bind field is in the form of schema.element.qualifier=>metadata_field, split it and get the + // metadata field + if (typeBindField.contains(KEY_VALUE_SEPARATOR)) { + String[] parts = typeBindField.split(KEY_VALUE_SEPARATOR); + // Get the second part of the split - the metadata field + typeBFKey = parts[1]; + } + // Get the metadata value for the type-bind field + List documentType = itemService.getMetadataByMetadataString(obj.getItem(), typeBFKey); + if (documentType.size() > 0) { + documentTypeValueList.add(documentType.get(0).getValue()); + } } - // Get list of all field names (including qualdrop names) allowed for this dc.type - List allowedFieldNames = inputConfig.populateAllowedFieldNames(documentTypeValue); + // Get list of all field names (including qualdrop names) allowed for this dc.type, or specific type-bind field + List allowedFieldNames = new ArrayList<>(); + + if (CollectionUtils.isEmpty(documentTypeValueList)) { + // If no dc.type is set, we allow all fields + allowedFieldNames.addAll(inputConfig.populateAllowedFieldNames(null)); + } else { + documentTypeValueList.forEach(documentTypeValue -> { + allowedFieldNames.addAll(inputConfig.populateAllowedFieldNames(documentTypeValue)); + }); + } - // Begin the actual validation loop for (DCInput[] row : inputConfig.getFields()) { for (DCInput input : row) { String fieldKey = - metadataAuthorityService.makeFieldKey(input.getSchema(), input.getElement(), input.getQualifier()); + metadataAuthorityService.makeFieldKey(input.getSchema(), input.getElement(), + input.getQualifier()); boolean isAuthorityControlled = metadataAuthorityService.isAuthorityControlled(fieldKey); List fieldsName = new ArrayList(); @@ -99,10 +125,10 @@ public List validate(SubmissionService submissionService, InProgressS // Check the lookup list. If no other inputs of the same field name allow this type, // then remove. This includes field name without qualifier. - if (!input.isAllowedFor(documentTypeValue) && (!allowedFieldNames.contains(fullFieldname) + if (!input.isAllowedFor(documentTypeValueList) && (!allowedFieldNames.contains(fullFieldname) && !allowedFieldNames.contains(input.getFieldName()))) { itemService.removeMetadataValues(ContextUtil.obtainCurrentRequestContext(), - obj.getItem(), mdv); + obj.getItem(), mdv); } else { validateMetadataValues(mdv, input, config, isAuthorityControlled, fieldKey, errors); if (mdv.size() > 0 && input.isVisible(DCInput.SUBMISSION_SCOPE)) { @@ -126,7 +152,7 @@ public List validate(SubmissionService submissionService, InProgressS for (String fieldName : fieldsName) { boolean valuesRemoved = false; List mdv = itemService.getMetadataByMetadataString(obj.getItem(), fieldName); - if (!input.isAllowedFor(documentTypeValue)) { + if (!input.isAllowedFor(documentTypeValueList)) { // Check the lookup list. If no other inputs of the same field name allow this type, // then remove. Otherwise, do not if (!(allowedFieldNames.contains(fieldName))) { @@ -134,26 +160,26 @@ public List validate(SubmissionService submissionService, InProgressS obj.getItem(), mdv); valuesRemoved = true; log.debug("Stripping metadata values for " + input.getFieldName() + " on type " - + documentTypeValue + " as it is allowed by another input of the same field " + + + documentTypeValueList + " as it is allowed by another input of the same field " + "name"); } else { log.debug("Not removing unallowed metadata values for " + input.getFieldName() + " on type " - + documentTypeValue + " as it is allowed by another input of the same field " + + + documentTypeValueList + " as it is allowed by another input of the same field " + "name"); } } validateMetadataValues(mdv, input, config, isAuthorityControlled, fieldKey, errors); if (((input.isRequired() && mdv.size() == 0) && input.isVisible(DCInput.SUBMISSION_SCOPE) - && !valuesRemoved) + && !valuesRemoved) || !isValidComplexDefinitionMetadata(input, mdv)) { // Is the input required for *this* type? In other words, are we looking at a required // input that is also allowed for this document type - if (input.isAllowedFor(documentTypeValue)) { + if (input.isAllowedFor(documentTypeValueList)) { // since this field is missing add to list of error // fields addError(errors, ERROR_VALIDATION_REQUIRED, "/" + WorkspaceItemRestRepository.OPERATION_PATH_SECTIONS + "/" + config.getId() + "/" + - input.getFieldName()); + input.getFieldName()); } } if (LOCAL_METADATA_HAS_CMDI.equals(fieldName)) { @@ -167,7 +193,9 @@ public List validate(SubmissionService submissionService, InProgressS } } } + } + return errors; } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ClarinUtils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ClarinUtils.java deleted file mode 100644 index 2a93f5793205..000000000000 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/ClarinUtils.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * The contents of this file are subject to the license and copyright - * detailed in the LICENSE and NOTICE files at the root of the source - * tree and available online at - * - * http://www.dspace.org/license/ - */ -package org.dspace.app.rest.utils; - -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import org.springframework.stereotype.Component; - -/** - * Collection of utility methods for clarin customized operations - * - * @author Milan Majchrak (dspace at dataquest.sk) - */ -@Component -public class ClarinUtils { - - private ClarinUtils() { - } - - /** - * Disables SSL certificate validation for the given connection - * - * @param connection - */ - public static void disableCertificateValidation(HttpsURLConnection connection) { - try { - // Create a TrustManager that trusts all certificates - TrustManager[] trustAllCerts = { new X509TrustManager() { - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return null; - } - - public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) { - } - - public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) { - } } - }; - - // Install the TrustManager - SSLContext sslContext = SSLContext.getInstance("SSL"); - sslContext.init(null, trustAllCerts, new SecureRandom()); - connection.setSSLSocketFactory(sslContext.getSocketFactory()); - - // Set a HostnameVerifier that accepts all hostnames - connection.setHostnameVerifier((hostname, session) -> true); - - } catch (NoSuchAlgorithmException | KeyManagementException e) { - throw new RuntimeException("Error disabling SSL certificate validation", e); - } - } -} diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java index ed6e26ed0fb7..4d47e6484b64 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/utils/Utils.java @@ -29,6 +29,11 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; @@ -43,7 +48,17 @@ import java.util.Set; import java.util.TreeSet; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.annotation.Nullable; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; @@ -1076,4 +1091,157 @@ private BaseObjectRest findBaseObjectRest(Context context, String apiCategory, S context.restoreAuthSystemState(); } } + + /** + * Disables SSL certificate validation for the given connection + * + * @param connection + */ + public static void disableCertificateValidation(HttpsURLConnection connection) { + try { + // Create a TrustManager that trusts all certificates + TrustManager[] trustAllCerts = { new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + + public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) { + } } + }; + + // Install the TrustManager + SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(null, trustAllCerts, new SecureRandom()); + connection.setSSLSocketFactory(sslContext.getSocketFactory()); + + // Set a HostnameVerifier that accepts all hostnames + connection.setHostnameVerifier((hostname, session) -> true); + + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw new RuntimeException("Error disabling SSL certificate validation", e); + } + } + + /** + * Function to encode only non-ASCII characters + */ + public static String encodeNonAsciiCharacters(String input) { + StringBuilder result = new StringBuilder(); + for (char ch : input.toCharArray()) { + if (!StringUtils.isAsciiPrintable(String.valueOf(ch))) { // Use Apache Commons method + result.append(URLEncoder.encode(String.valueOf(ch), StandardCharsets.UTF_8)); + } else { + result.append(ch); // Leave ASCII characters intact + } + } + return result.toString(); + } + + /** + * Update the solr DiscoverQuery because in some cases it won't search properly numbers and characters together. + * @param searchValue searching value + * @param searchField it could be special solr index or metadata field + * @return updated DiscoverQuery + */ + public static String normalizeDiscoverQuery(String searchValue, + String searchField) { + // regex if searchValue consist of numbers and characters + // \d - digit + String regexNumber = "(.)*(\\d)(.)*"; + // \D - non digit + String regexString = "(.)*(\\D)(.)*"; + Pattern patternNumber = Pattern.compile(regexNumber); + Pattern patternString = Pattern.compile(regexString); + // if the searchValue is mixed with numbers and characters the Solr ignore numbers by default + // divide the characters and numbers from searchValue to the separate queries and from separate queries + // create one complex query + if (patternNumber.matcher(searchValue).matches() && patternString.matcher(searchValue).matches()) { + List characterList = extractCharacterListFromString(searchValue); + List numberList = extractNumberListFromString(searchValue); + return composeQueryWithNumbersAndChars(searchField, characterList, numberList); + } + return null; + } + + /** + * From searchValue get all number values which are separated by the number to the List of Strings. + * @param searchValue e.g. 'my1Search2' + * @return e.g. [1, 2] + */ + private static List extractNumberListFromString(String searchValue) { + List numberList = new ArrayList<>(); + + // get numbers from searchValue as List + Pattern numberRegex = Pattern.compile("-?\\d+"); + Matcher numberMatcher = numberRegex.matcher(searchValue); + while (numberMatcher.find()) { + numberList.add(numberMatcher.group()); + } + + return numberList; + } + + /** + * From searchValue get all String values which are separated by the number to the List of Strings. + * @param searchValue e.g. 'my1Search2' + * @return e.g. [my, Search] + */ + private static List extractCharacterListFromString(String searchValue) { + List characterList = null; + // get characters from searchValue as List + searchValue = searchValue.replaceAll("[0-9]", " "); + characterList = new LinkedList<>(Arrays.asList(searchValue.split(" "))); + // remove empty characters from the characterList + characterList.removeIf(characters -> characters == null || "".equals(characters)); + + return characterList; + } + + /** + * From list of String and list of Numbers create a query for the SolrQuery. + * @param metadataField e.g. `dc.contributor.author` + * @param characterList e.g. [my, Search] + * @param numberList e.g. [1, 2] + * @return "dc.contributor.author:*my* AND dc.contributor.author:*Search* AND dc.contributor.author:*1* AND ..." + */ + private static String composeQueryWithNumbersAndChars(String metadataField, List characterList, + List numberList) { + addQueryTemplateToList(metadataField, characterList); + addQueryTemplateToList(metadataField, numberList); + + String joinedChars = String.join(" AND ", characterList); + String joinedNumbers = String.join(" AND ", numberList); + return joinedChars + " AND " + joinedNumbers; + + } + + /** + * Add SolrQuery template to the every item of the List + * @param metadataField e.g. `dc.contributor.author` + * @param stringList could be List of String or List of Numbers which are in the String format because of Solr + * e.g. [my, Search] + * @return [dc.contributor.author:*my*, dc.contributor.author:*Search*] + */ + private static List addQueryTemplateToList(String metadataField, List stringList) { + String template = metadataField + ":" + "*" + " " + "*"; + + AtomicInteger index = new AtomicInteger(); + stringList.forEach(characters -> { + String queryString = template.replaceAll(" ", characters); + stringList.set(index.getAndIncrement(), queryString); + }); + return stringList; + } + + /** + * Filter unique values from the list and return list with unique values + * @return List with unique values + */ + public static Predicate distinctByKey(Function keyExtractor) { + Map map = new ConcurrentHashMap<>(); + return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java index 57a8dbb3c2b7..e1f9788a517e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/BrowsesResourceControllerIT.java @@ -1422,7 +1422,7 @@ public void testBrowseByEntriesStartsWith() throws Exception { //Verify that the index filters to the "Universe" entries and Counts 2 Items. .andExpect(jsonPath("$._embedded.entries", - contains(BrowseEntryResourceMatcher.matchBrowseEntry("Universe", 2) + contains(BrowseEntryResourceMatcher.matchBrowseEntry("Universe".toLowerCase(), 2) ))) //Verify startsWith parameter is included in the links .andExpect(jsonPath("$._links.self.href", containsString("?startsWith=U"))); @@ -1446,7 +1446,8 @@ public void testBrowseByEntriesStartsWith() throws Exception { //Verify that the index filters to the "Turing, Alan'" items. .andExpect(jsonPath("$._embedded.entries", - contains(BrowseEntryResourceMatcher.matchBrowseEntry("Turing, Alan Mathison", 1) + contains(BrowseEntryResourceMatcher.matchBrowseEntry( + "Turing, Alan Mathison".toLowerCase(), 1) ))) //Verify that the startsWith paramater is included in the links .andExpect(jsonPath("$._links.self.href", containsString("?startsWith=T"))); @@ -1469,7 +1470,8 @@ public void testBrowseByEntriesStartsWith() throws Exception { //Verify that the index filters to the "Computing'" items. .andExpect(jsonPath("$._embedded.entries", - contains(BrowseEntryResourceMatcher.matchBrowseEntry("Computing", 3) + contains(BrowseEntryResourceMatcher.matchBrowseEntry( + "Computing".toLowerCase(), 3) ))) //Verify that the startsWith paramater is included in the links .andExpect(jsonPath("$._links.self.href", containsString("?startsWith=C"))); @@ -1544,9 +1546,12 @@ public void testBrowseByEntriesStartsWithAndDiacritics() throws Exception { //Verify that the index filters to the "Alonso, Nombre", "Álvarez, Nombre" and "Azuaga, Nombre" // and diacritics are ignored in sorting .andExpect(jsonPath("$._embedded.entries", - contains(BrowseEntryResourceMatcher.matchBrowseEntry("Alonso, Nombre", 1), - BrowseEntryResourceMatcher.matchBrowseEntry("Álvarez, Nombre", 1), - BrowseEntryResourceMatcher.matchBrowseEntry("Azuaga, Nombre", 1) + contains(BrowseEntryResourceMatcher.matchBrowseEntry( + "Alonso, Nombre".toLowerCase(), 1), + BrowseEntryResourceMatcher.matchBrowseEntry( + "Álvarez, Nombre".toLowerCase(), 1), + BrowseEntryResourceMatcher.matchBrowseEntry( + "Azuaga, Nombre".toLowerCase(), 1) ))) //Verify startsWith parameter is included in the links @@ -1570,8 +1575,10 @@ public void testBrowseByEntriesStartsWithAndDiacritics() throws Exception { //Verify that the index filters to the "Ögren, Name"" and "Ortiz, Nombre" .andExpect(jsonPath("$._embedded.entries", - contains(BrowseEntryResourceMatcher.matchBrowseEntry("Ögren, Name", 1), - BrowseEntryResourceMatcher.matchBrowseEntry("Ortiz, Nombre", 1) + contains(BrowseEntryResourceMatcher.matchBrowseEntry( + "Ögren, Name".toLowerCase(), 1), + BrowseEntryResourceMatcher.matchBrowseEntry( + "Ortiz, Nombre".toLowerCase(), 1) ))) //Verify that the startsWith paramater is included in the links .andExpect(jsonPath("$._links.self.href", containsString("?startsWith=Ó"))); @@ -1596,9 +1603,12 @@ public void testBrowseByEntriesStartsWithAndDiacritics() throws Exception { //Verify that the index filters to the "Telecomunicaciones', "Teléfono" and "Televisor" and // it is sorted ignoring diacritics .andExpect(jsonPath("$._embedded.entries", - contains(BrowseEntryResourceMatcher.matchBrowseEntry("Telecomunicaciones", 1), - BrowseEntryResourceMatcher.matchBrowseEntry("Teléfono", 1), - BrowseEntryResourceMatcher.matchBrowseEntry("Televisor", 1) + contains(BrowseEntryResourceMatcher.matchBrowseEntry( + "Telecomunicaciones".toLowerCase(), 1), + BrowseEntryResourceMatcher.matchBrowseEntry( + "Teléfono".toLowerCase(), 1), + BrowseEntryResourceMatcher.matchBrowseEntry( + "Televisor".toLowerCase(), 1) ))) //Verify that the startsWith paramater is included in the links .andExpect(jsonPath("$._links.self.href", containsString("?startsWith=Tele"))); @@ -1621,7 +1631,7 @@ public void testBrowseByEntriesStartsWithAndDiacritics() throws Exception { //Verify that the index filters to the "Guion" .andExpect(jsonPath("$._embedded.entries", - contains(BrowseEntryResourceMatcher.matchBrowseEntry("Guion", 1) + contains(BrowseEntryResourceMatcher.matchBrowseEntry("Guion".toLowerCase(), 1) ))) //Verify that the startsWith paramater is included in the links .andExpect(jsonPath("$._links.self.href", containsString("?startsWith=Guión"))); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinBitstreamImportControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinBitstreamImportControllerIT.java index 9e0c0f339a7c..74ad6b65e687 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinBitstreamImportControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinBitstreamImportControllerIT.java @@ -270,6 +270,41 @@ public void importBitstreamForExistingFileValidationErrorTest() throws Exception assertEquals(bitstreamService.findAll(context).size(), 0); } + @Test + public void importDeletedBitstreamTest() throws Exception { + //input data + ObjectNode checksumNode = jsonNodeFactory.objectNode(); + checksumNode.set("checkSumAlgorithm", null); + checksumNode.set("value", null); + ObjectNode node = jsonNodeFactory.objectNode(); + node.set("sizeBytes", null); + node.set("checkSum", checksumNode); + + //create new bitstream for existing file + ObjectMapper mapper = new ObjectMapper(); + uuid = UUID.fromString(read( getClient(token).perform(post("/api/clarin/import/core/bitstream") + .content(mapper.writeValueAsBytes(node)) + .contentType(contentType) + .param("internal_id", internalId) + .param("storeNumber", "0") + .param("deleted", "true")) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(), + "$.id")); + + bitstream = bitstreamService.find(context, uuid); + assertEquals(bitstream.getSizeBytes(), 0); + assertEquals(bitstream.getInternalId(), internalId); + assertEquals(bitstream.getStoreNumber(), 0); + assertEquals(bitstream.getSequenceID(), -1); + assertEquals(bitstream.isDeleted(), true); + + //clean all + context.turnOffAuthorisationSystem(); + BitstreamBuilder.deleteBitstream(uuid); + context.restoreAuthSystemState(); + } + private void checkCreatedBitstream(UUID uuid, String internalId, int storeNumber, String bitstreamFormat, int sequence, boolean deleted, long sizeBytes, String checkSum) throws SQLException { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinDiscoJuiceFeedsControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinDiscoJuiceFeedsControllerIT.java index 0075011fd0bf..d20298ac9116 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinDiscoJuiceFeedsControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinDiscoJuiceFeedsControllerIT.java @@ -22,7 +22,7 @@ import org.apache.commons.lang3.StringUtils; import org.dspace.app.rest.test.AbstractControllerIntegrationTest; -import org.dspace.app.rest.utils.ClarinUtils; +import org.dspace.app.rest.utils.Utils; import org.dspace.services.ConfigurationService; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; @@ -64,7 +64,7 @@ public void testDiscoFeedURL() throws Exception { // Disable SSL certificate validation if (disableSSL && conn instanceof HttpsURLConnection) { - ClarinUtils.disableCertificateValidation((HttpsURLConnection) conn); + Utils.disableCertificateValidation((HttpsURLConnection) conn); } Object obj = parser.parse(new InputStreamReader(conn.getInputStream())); diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinDiscoveryRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinDiscoveryRestControllerIT.java index d8aca96b37b3..cd2829dc626a 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinDiscoveryRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinDiscoveryRestControllerIT.java @@ -74,7 +74,7 @@ * This class is modified version of the DiscoveryRestControllerIT. Because for CLARIN customization * the facets/filters was updated and the tests were failing. So the test class had must be updated. * - * @author Milan Majchrak (milan.majchrak at dataquest.sk) + * @author Milan Majchrak (dspace at dataquest.sk) */ public class ClarinDiscoveryRestControllerIT extends AbstractControllerIntegrationTest { @Autowired @@ -358,8 +358,8 @@ public void discoverFacetsAuthorTestWithPrefix() throws Exception { // up in different items //These authors are order according to count. Only two show up because of the prefix. .andExpect(jsonPath("$._embedded.values", containsInAnyOrder( - FacetValueMatcher.entryAuthor("Smith, Maria"), - FacetValueMatcher.entryAuthor("Smith, Donald") + FacetValueMatcher.entryAuthor("Smith, Maria".toLowerCase()), + FacetValueMatcher.entryAuthor("Smith, Donald".toLowerCase()) ))) ; } @@ -1003,7 +1003,8 @@ public void discoverSearchTest() throws Exception { SearchFilterMatcher.clarinItemsCommunityFilter(), SearchFilterMatcher.clarinItemsTypeFilter(), SearchFilterMatcher.clarinSubjectFirstValueFilter(), - SearchFilterMatcher.clarinDataProviderFacet() + SearchFilterMatcher.clarinDataProviderFacet(), + SearchFilterMatcher.dcTypeFilter() ))) //These sortOptions need to be present as it's the default in the configuration .andExpect(jsonPath("$.sortOptions", contains( diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkflowItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkflowItemRestRepositoryIT.java index 20ec8dc38f24..b96788b2083f 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkflowItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkflowItemRestRepositoryIT.java @@ -54,7 +54,7 @@ /** * For testing the ClarinVersionedHandleIdentifierProvider * - * @author Milan Majchrak (milan.majchrak at dataquest.sk) + * @author Milan Majchrak (dspace at dataquest.sk) */ public class ClarinWorkflowItemRestRepositoryIT extends AbstractControllerIntegrationTest { @@ -320,4 +320,49 @@ public void shouldCreateProvenanceMessageOnItemSubmit() throws Exception { } assertThat(containsSubmitterProvenance, is(true)); } + + // When some input field has ... in the submission-forms.xml + @Test + public void shouldCreateItemWithCustomTypeBindField() throws Exception { + context.turnOffAuthorisationSystem(); + String CITATION_VALUE = "Some citation"; + + //** GIVEN ** + //1. A community with one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + // Submitter group - allow deposit a new item without workflow + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .build(); + + //3. a workspace item + WorkspaceItem wsitem = WorkspaceItemBuilder.createWorkspaceItem(context, col1) + .withTitle("Type-bind test") + .withIssueDate("2017-10-17") + .grantLicense() + .withMetadata("dc", "identifier", "citation", CITATION_VALUE) + .build(); + + context.restoreAuthSystemState(); + + // get the submitter auth token + String authToken = getAuthToken(admin.getEmail(), password); + + // submit the workspaceitem to start the workflow + getClient(authToken) + .perform(post(BASE_REST_SERVER_URL + "/api/workflow/workflowitems") + .content("/api/submission/workspaceitems/" + wsitem.getID()) + .contentType(textUriContentType)) + .andExpect(status().isCreated()); + + // Load deposited item and check the provenance metadata + Item depositedItem = itemService.find(context, wsitem.getItem().getID()); + List mvList = itemService.getMetadata(depositedItem, "dc", "identifier", + "citation", Item.ANY); + assertFalse(mvList.isEmpty()); + assertThat(mvList.get(0).getValue(), is(CITATION_VALUE)); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkspaceItemRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkspaceItemRestRepositoryIT.java index 1b55cc44288a..8decdcb4d143 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkspaceItemRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/ClarinWorkspaceItemRestRepositoryIT.java @@ -967,6 +967,30 @@ public void createItemWhenSpecificHandleHasNoSubprefix() throws Exception { context.restoreAuthSystemState(); } + @Test + public void testWsiWithShareToken() throws Exception { + String shareToken = "1234567890"; + context.turnOffAuthorisationSystem(); + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection").build(); + WorkspaceItem wItem = WorkspaceItemBuilder.createWorkspaceItem(context, col) + .withTitle("Item with custom handle") + .withIssueDate("2017-10-17") + .withShareToken(shareToken) + .build(); + context.restoreAuthSystemState(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/submission/workspaceitems/search/shareToken") + .param("shareToken", shareToken) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._embedded.workspaceitems[0].id", is(wItem.getID()))); + } + /** * Create Clarin License Label object for testing purposes. */ diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionControllerIT.java new file mode 100644 index 000000000000..957f275220e1 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SubmissionControllerIT.java @@ -0,0 +1,116 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static com.jayway.jsonpath.JsonPath.read; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.concurrent.atomic.AtomicReference; +import javax.ws.rs.core.MediaType; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.EPersonBuilder; +import org.dspace.builder.WorkspaceItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.WorkspaceItem; +import org.dspace.content.service.WorkspaceItemService; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.service.EPersonService; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration test for the SubmissionController + * + * @author Milan Majchrak (dspace at dataquest.sk) + */ +public class SubmissionControllerIT extends AbstractControllerIntegrationTest { + + private static final String SUBMITTER_EMAIL = "submitter@example.com"; + @Autowired + private WorkspaceItemService workspaceItemService; + @Autowired + private EPersonService ePersonService; + + WorkspaceItem wsi; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + context.turnOffAuthorisationSystem(); + //** GIVEN ** + //1. A community with one collection. + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + //2. create a normal user to use as submitter + EPerson submitter = EPersonBuilder.createEPerson(context) + .withEmail(SUBMITTER_EMAIL) + .withPassword("dspace") + .build(); + + // Submitter group - allow deposit a new item without workflow + Collection col = CollectionBuilder.createCollection(context, parentCommunity) + .withName("Collection") + .withSubmitterGroup(submitter) + .build(); + + wsi = WorkspaceItemBuilder.createWorkspaceItem(context, col) + .withTitle("Item with custom handle") + .withIssueDate("2017-10-17") + .withSubmitter(submitter) + .build(); + context.restoreAuthSystemState(); + } + + @Test + public void generateShareTokenAndSetOwnerTest() throws Exception { + AtomicReference shareLink = new AtomicReference<>(); + EPerson currentUser = context.getCurrentUser(); + + String adminToken = getAuthToken(admin.getEmail(), password); + getClient(adminToken).perform(get("/api/submission/share") + .param("workspaceitemid", wsi.getID().toString()) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.shareLink", is(notNullValue()))) + .andDo(result -> shareLink.set(read(result.getResponse().getContentAsString(), "$.shareLink"))); + + // Check that the share token was set on the WorkspaceItem and persisted into the database + WorkspaceItem updatedWsi = workspaceItemService.find(context, wsi.getID()); + assertThat(wsi.getID(), is(updatedWsi.getID())); + assertThat(updatedWsi.getSubmitter().getEmail(), is(SUBMITTER_EMAIL)); + assertThat(updatedWsi.getSubmitter().getEmail(), not(currentUser.getEmail())); + + EPerson adminUser = ePersonService.findByEmail(context, admin.getEmail()); + context.setCurrentUser(adminUser); + // Set workspace item owner to the current user + getClient(adminToken).perform(get("/api/submission/setOwner") + .param("shareToken", updatedWsi.getShareToken()) + .param("workspaceitemid", updatedWsi.getID().toString()) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + // Check that the owner of the WorkspaceItem was set to the current user + // Check the wsi was persisted into the database + updatedWsi = workspaceItemService.find(context, wsi.getID()); + assertThat(updatedWsi.getSubmitter().getEmail(), is(adminUser.getEmail())); + assertThat(updatedWsi.getSubmitter().getEmail(), not(SUBMITTER_EMAIL)); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/SuggestionRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SuggestionRestControllerIT.java new file mode 100644 index 000000000000..a449bfd29bc5 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/SuggestionRestControllerIT.java @@ -0,0 +1,176 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.dspace.builder.CollectionBuilder; +import org.dspace.builder.CommunityBuilder; +import org.dspace.builder.ItemBuilder; +import org.dspace.content.Collection; +import org.dspace.content.Item; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; + +/** + * Integration test for the {@link org.dspace.app.rest.repository.SuggestionRestController} + * + * @author Milan Majchrak (dspace at dataquest.sk) + */ +public class SuggestionRestControllerIT extends AbstractControllerIntegrationTest { + + private Item publicItem; + private Collection col; + private final String SUBJECT_SEARCH_VALUE = "test subject"; + private final String LANGUAGE_SEARCH_VALUE_KEY = "Alumu-Tesu"; + private final String LANGUAGE_SEARCH_VALUE_VALUE = "aab"; + private final String ITEM_TITLE = "Item title"; + + @Before + public void setup() throws Exception { + context.turnOffAuthorisationSystem(); + // 1. A community-collection structure with one parent community and one collection + parentCommunity = CommunityBuilder.createCommunity(context) + .withName("Parent Community") + .build(); + + col = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection").build(); + + // 2. Create item and add it to the collection + publicItem = ItemBuilder.createItem(context, col) + .withTitle(ITEM_TITLE) + .withMetadata("dc", "subject", null, SUBJECT_SEARCH_VALUE ) + .build(); + + context.restoreAuthSystemState(); + } + + /** + * Should return formatted suggestions in the VocabularyEntryRest objects + */ + @Test + public void testSearchBySubjectAcSolrIndex() throws Exception { + String userToken = getAuthToken(eperson.getEmail(), password); + // substring = find only by the `test` value + getClient(userToken).perform(get("/api/suggestions?autocompleteCustom=solr-subject_ac&searchValue=" + + SUBJECT_SEARCH_VALUE.substring(0, 4))) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.vocabularyEntryRests", Matchers.hasItem( + allOf( + hasJsonPath("$.display", is(SUBJECT_SEARCH_VALUE)), + hasJsonPath("$.value", is(SUBJECT_SEARCH_VALUE)), + hasJsonPath("$.type", is("vocabularyEntry")) + )))); + } + + /** + * Should return no suggestions + */ + @Test + public void testSearchBySubjectAcSolrIndex_noResults() throws Exception { + String userToken = getAuthToken(eperson.getEmail(), password); + // substring = find only by the `test` value + getClient(userToken).perform(get("/api/suggestions?autocompleteCustom=solr-subject_ac&searchValue=" + + "no such subject")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded.vocabularyEntryRests").doesNotExist()); + } + + /** + * Should return suggestions from the JSON file + */ + @Test + public void testSearchByLanguageFromJson() throws Exception { + String userToken = getAuthToken(eperson.getEmail(), password); + getClient(userToken).perform( + get("/api/suggestions?autocompleteCustom=json_static-iso_langs.json&searchValue=" + + LANGUAGE_SEARCH_VALUE_KEY)) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$._embedded.vocabularyEntryRests", Matchers.hasItem( + allOf( + hasJsonPath("$.display", is(LANGUAGE_SEARCH_VALUE_KEY)), + hasJsonPath("$.value", is(LANGUAGE_SEARCH_VALUE_VALUE)), + hasJsonPath("$.type", is("vocabularyEntry")) + )))); + } + + /** + * Should return no suggestions from the JSON file + */ + @Test + public void testSearchByLanguageFromJson_noResults() throws Exception { + String userToken = getAuthToken(eperson.getEmail(), password); + getClient(userToken).perform( + get("/api/suggestions?autocompleteCustom=json_static-iso_langs.json&searchValue=" + + "no such language")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded.vocabularyEntryRests").doesNotExist()); + } + + /** + * Should return suggestions from the solr `title_ac` index. + * Compose specific query from the definition and the search value. + */ + @Test + public void testSearchBySpecificQueryFromSolr() throws Exception { + String userToken = getAuthToken(eperson.getEmail(), password); + getClient(userToken).perform( + get("/api/suggestions?autocompleteCustom=solr-title_ac?query=title_ac:**&searchValue=" + + ITEM_TITLE.substring(0, 4))) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$._embedded.vocabularyEntryRests", Matchers.hasItem( + allOf( + hasJsonPath("$.display", is(ITEM_TITLE)), + hasJsonPath("$.value", is(ITEM_TITLE)), + hasJsonPath("$.type", is("vocabularyEntry")) + )))); + } + + /** + * Should return suggestions from the solr `title_ac` index. + * Compose specific query from the definition and the search value. + */ + @Test + public void testSearchBySpecificQueryFromSolr_noresults() throws Exception { + String userToken = getAuthToken(eperson.getEmail(), password); + getClient(userToken).perform( + get("/api/suggestions?autocompleteCustom=solr-title_ac?query=title_ac:**&searchValue=" + + "no such title")) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", is(0))) + .andExpect(jsonPath("$._embedded.vocabularyEntryRests").doesNotExist()); + } + + /** + * Should return 401 Forbidden + */ + @Test + public void testShouldNotAuthorized() throws Exception { + getClient().perform(get("/api/suggestions?autocompleteCustom=solr-title_ac?query=title_ac:**&searchValue=" + + "no such title")) + .andExpect(status().isUnauthorized()); + } +} diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/hdlresolver/HdlResolverRestControllerIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/hdlresolver/HdlResolverRestControllerIT.java index 8227caffe616..f19f4944478e 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/hdlresolver/HdlResolverRestControllerIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/hdlresolver/HdlResolverRestControllerIT.java @@ -9,6 +9,7 @@ import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -84,7 +85,36 @@ public void givenMappedIdentifierWhenCallHdlresolverThenReturnsMappedURL() throw getClient() .perform(get("/wrongController/" + publicItem1.getHandle())) .andExpect(status().isNotFound()); + } + + @Test + public void givenMappedIdentifierWhenCallHdlresolverThenReturnsMappedParams() throws Exception { + context.turnOffAuthorisationSystem(); + + // ** START GIVEN ** + parentCommunity = CommunityBuilder.createCommunity(context).withName("Parent Community").build(); + + Collection col1 = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1") + .withLogo("TestingContentForLogo").build(); + Item publicItem1 = ItemBuilder.createItem(context, col1).withTitle("Public item 1").withIssueDate("2017-10-17") + .withAuthor("Smith, Donald").withAuthor("Doe, John").withSubject("ExtraEntry") + .withHandle("123456789/testHdlResolver").build(); + + context.restoreAuthSystemState(); + + // ** END GIVEN ** + getClient() + .perform(get(HdlResolverRestController.RESOLVE + publicItem1.getHandle()) + .param("metadata", "true")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.URL", + StringContains.containsString("123456789/testHdlResolver"))) + .andExpect(jsonPath("$.TITLE", StringContains.containsString("Public item 1"))) + .andExpect(jsonPath("$.REPOSITORY", is(configurationService.getProperty("dspace.name")))) + .andExpect(jsonPath("$.REPORTEMAIL", + StringContains.containsString("dspace-help@ufal.mff.cuni.cz"))) + .andExpect(jsonPath("$.SUBMITDATE").exists()); } @Test diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java index 23d66754dcc6..939873b5c4ba 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/FacetEntryMatcher.java @@ -131,6 +131,16 @@ public static Matcher clarinLicenseRightsFacet(boolean hasNext) ); } + public static Matcher dcTypeFacet(boolean hasNext) { + return allOf( + hasJsonPath("$.name", is("dctype")), + hasJsonPath("$.facetType", is("text")), + hasJsonPath("$.facetLimit", any(Integer.class)), + hasJsonPath("$._links.self.href", containsString("api/discover/facets/dctype")), + hasJsonPath("$._links", matchNextLink(hasNext, "api/discover/facets/dctype")) + ); + } + public static Matcher clarinItemsLanguageFacet(boolean hasNext) { return allOf( hasJsonPath("$.name", is("language")), @@ -162,11 +172,11 @@ public static Matcher clarinItemsCommunityFacet(boolean hasNext) */ public static Matcher typeFacet(boolean b) { return allOf( - hasJsonPath("$.name", is("itemtype")), + hasJsonPath("$.name", is("type")), hasJsonPath("$.facetType", is("text")), hasJsonPath("$.facetLimit", any(Integer.class)), - hasJsonPath("$._links.self.href", containsString("api/discover/facets/itemtype")), - hasJsonPath("$._links", matchNextLink(b, "api/discover/facets/itemtype")) + hasJsonPath("$._links.self.href", containsString("api/discover/facets/type")), + hasJsonPath("$._links", matchNextLink(b, "api/discover/facets/type")) ); } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SearchFilterMatcher.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SearchFilterMatcher.java index b5533a955505..c708ca96b98c 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SearchFilterMatcher.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/matcher/SearchFilterMatcher.java @@ -188,7 +188,7 @@ public static Matcher clarinItemsCommunityFilter() { public static Matcher clarinItemsTypeFilter() { return allOf( - hasJsonPath("$.filter", is("itemtype")), + hasJsonPath("$.filter", is("type")), hasJsonPath("$.hasFacets", is(true)), hasJsonPath("$.type", is("text")), hasJsonPath("$.openByDefault", is(false)), @@ -226,4 +226,14 @@ public static Matcher clarinDataProviderFacet() { checkOperators() ); } + + public static Matcher dcTypeFilter() { + return allOf( + hasJsonPath("$.filter", is("dctype")), + hasJsonPath("$.hasFacets", is(false)), + hasJsonPath("$.type", is("text")), + hasJsonPath("$.openByDefault", is(false)), + checkOperators() + ); + } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/ClarinShibbolethLoginFilterIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/ClarinShibbolethLoginFilterIT.java index 8b62e95bed79..e828dd9dc2d7 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/ClarinShibbolethLoginFilterIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/security/ClarinShibbolethLoginFilterIT.java @@ -9,6 +9,7 @@ import static org.dspace.app.rest.security.ShibbolethLoginFilterIT.PASS_ONLY; import static org.dspace.app.rest.security.clarin.ClarinShibbolethLoginFilter.VERIFICATION_TOKEN_HEADER; +import static org.dspace.rdf.negotiation.MediaRange.token; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -19,6 +20,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -53,8 +55,12 @@ public class ClarinShibbolethLoginFilterIT extends AbstractControllerIntegrationTest { public static final String[] SHIB_ONLY = {"org.dspace.authenticate.clarin.ClarinShibAuthentication"}; + private static final String NET_ID_EPPN_HEADER = "eppn"; + private static final String NET_ID_PERSISTENT_ID = "persistent-id"; private static final String NET_ID_TEST_EPERSON = "123456789"; private static final String IDP_TEST_EPERSON = "Test Idp"; + private static final String KNIHOVNA_KUN_TEST_ZLUTOUCKY = "knihovna Kůň test Žluťoučký"; + private EPersonRest ePersonRest; private final String feature = CanChangePasswordFeature.NAME; @@ -183,10 +189,7 @@ public void userFillInEmailAndShouldBeRegisteredByVerificationToken() throws Exc .andExpect(status().isOk()); // Check if was created a user with such email and netid. - EPerson ePerson = ePersonService.findByNetid(context, Util.formatNetId(netId, idp)); - assertTrue(Objects.nonNull(ePerson)); - assertEquals(ePerson.getEmail(), email); - assertEquals(ePerson.getNetid(), Util.formatNetId(netId, idp)); + EPerson ePerson = checkUserWasCreated(netId, idp, email, null); // The user is registered now log him getClient().perform(post("/api/authn/shibboleth") @@ -207,7 +210,7 @@ public void userFillInEmailAndShouldBeRegisteredByVerificationToken() throws Exc .andExpect(status().isFound()); // Delete created eperson - clean after the test - EPersonBuilder.deleteEPerson(ePerson.getID()); + deleteShibbolethUser(ePerson); } @Test @@ -226,11 +229,7 @@ public void testShouldReturnDuplicateUserError() throws Exception { .andExpect(redirectedUrl("http://localhost:4000")) .andReturn().getResponse().getHeader("Authorization"); - - getClient(token).perform(get("/api/authn/status")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.authenticated", is(true))) - .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))); + checkUserIsSignedIn(token); // Check if was created a user with such email and netid. EPerson ePerson = ePersonService.findByNetid(context, Util.formatNetId(netId, IDP_TEST_EPERSON)); @@ -252,6 +251,36 @@ public void testShouldReturnDuplicateUserError() throws Exception { EPersonBuilder.deleteEPerson(ePerson.getID()); } + // Login with email without netid, but the user with such email already exists and it has assigned netid. + @Test + public void testShouldReturnDuplicateUserErrorLoginWithoutNetId() throws Exception { + String email = "test@email.sk"; + String netId = email; + + // login through shibboleth + String token = getClient().perform(get("/api/authn/shibboleth") + .header("SHIB-MAIL", email) + .header("SHIB-NETID", netId) + .header("Shib-Identity-Provider", IDP_TEST_EPERSON)) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("http://localhost:4000")) + .andReturn().getResponse().getHeader("Authorization"); + + checkUserIsSignedIn(token); + + // Should not login because the user with such email already exists + getClient().perform(get("/api/authn/shibboleth") + .header("SHIB-MAIL", email) + .header("Shib-Identity-Provider", IDP_TEST_EPERSON)) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("http://localhost:4000/login/duplicate-user?email=" + email)) + .andReturn().getResponse().getHeader("Authorization"); + + // Check if was created a user with such email and netid. + EPerson ePerson = checkUserWasCreated(netId, IDP_TEST_EPERSON, email, null); + deleteShibbolethUser(ePerson); + } + // This test is copied from the `ShibbolethLoginFilterIT` and modified following the Clarin updates. @Test public void testRedirectToGivenTrustedUrl() throws Exception { @@ -264,10 +293,7 @@ public void testRedirectToGivenTrustedUrl() throws Exception { .andExpect(redirectedUrl("http://localhost:8080/server/api/authn/status")) .andReturn().getResponse().getHeader("Authorization"); - getClient(token).perform(get("/api/authn/status")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.authenticated", is(true))) - .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))); + checkUserIsSignedIn(token); getClient(token).perform( get("/api/authz/authorizations/search/object") @@ -299,11 +325,7 @@ public void patchPassword() throws Exception { .andExpect(redirectedUrl("http://localhost:4000")) .andReturn().getResponse().getHeader("Authorization"); - - getClient(token).perform(get("/api/authn/status")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.authenticated", is(true))) - .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))); + checkUserIsSignedIn(token); // updates password getClient(token).perform(patch("/api/eperson/epersons/" + clarinEperson.getID()) @@ -328,11 +350,7 @@ public void testRedirectToDefaultDspaceUrl() throws Exception { .andExpect(redirectedUrl("http://localhost:4000")) .andReturn().getResponse().getHeader("Authorization"); - - getClient(token).perform(get("/api/authn/status")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.authenticated", is(true))) - .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))); + checkUserIsSignedIn(token); getClient(token).perform( get("/api/authz/authorizations/search/object") @@ -463,28 +481,10 @@ public void testISOShibHeaders() throws Exception { .andExpect(redirectedUrl("http://localhost:4000")) .andReturn().getResponse().getHeader("Authorization"); - - getClient(token).perform(get("/api/authn/status")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.authenticated", is(true))) - .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))); - + checkUserIsSignedIn(token); // Check if was created a user with such email and netid. - EPerson ePerson = ePersonService.findByNetid(context, Util.formatNetId(testNetId, testIdp)); - assertTrue(Objects.nonNull(ePerson)); - assertEquals(ePerson.getEmail(), testMail); - assertEquals(ePerson.getFirstName(), "knihovna Kůň test Žluťoučký"); - - EPersonBuilder.deleteEPerson(ePerson.getID()); - - getClient(token).perform( - get("/api/authz/authorizations/search/object") - .param("embed", "feature") - .param("feature", feature) - .param("uri", utils.linkToSingleResource(ePersonRest, "self").getHref())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.page.totalElements", is(0))) - .andExpect(jsonPath("$._embedded").doesNotExist()); + EPerson ePerson = checkUserWasCreated(testNetId, testIdp, testMail, KNIHOVNA_KUN_TEST_ZLUTOUCKY); + deleteShibbolethUser(ePerson); } @Test @@ -500,25 +500,208 @@ public void testUTF8ShibHeaders() throws Exception { .header("SHIB-MAIL", testMail) .header("Shib-Identity-Provider", testIdp) .header("SHIB-NETID", testNetId) - .header("SHIB-GIVENNAME", "knihovna Kůň test Žluťoučký")) + .header("SHIB-GIVENNAME", KNIHOVNA_KUN_TEST_ZLUTOUCKY)) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("http://localhost:4000")) + .andReturn().getResponse().getHeader("Authorization"); + + checkUserIsSignedIn(token); + // Check if was created a user with such email and netid. + EPerson ePerson = checkUserWasCreated(testNetId, testIdp, testMail, KNIHOVNA_KUN_TEST_ZLUTOUCKY); + deleteShibbolethUser(ePerson); + } + + @Test + public void testRedirectToMissingHeadersWithRedirectUrlParam() throws Exception { + String expectedMissingHeadersUrl = configurationService.getProperty("dspace.ui.url") + "/login/missing-headers"; + + getClient().perform(get("/api/authn/shibboleth") + .param("redirectUrl", "http://localhost:8080/server/api/authn/status") + .header("SHIB-MAIL", clarinEperson.getEmail()) + .header("SHIB-NETID", NET_ID_TEST_EPERSON)) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl(expectedMissingHeadersUrl)); + } + + // eppn is set + @Test + public void testSuccessFullLoginEppnNetId() throws Exception { + String token = getClient().perform(get("/api/authn/shibboleth") + .header("Shib-Identity-Provider", IDP_TEST_EPERSON) + .header("SHIB-MAIL", clarinEperson.getEmail()) + .header(NET_ID_EPPN_HEADER, NET_ID_TEST_EPERSON)) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("http://localhost:4000")) .andReturn().getResponse().getHeader("Authorization"); + checkUserIsSignedIn(token); + EPerson ePerson = checkUserWasCreated(NET_ID_TEST_EPERSON, IDP_TEST_EPERSON, clarinEperson.getEmail(), null); + deleteShibbolethUser(ePerson); + } + + // persistent-id is set + @Test + public void testSuccessFullLoginPersistentIdNetId() throws Exception { + String token = getClient().perform(get("/api/authn/shibboleth") + .header("Shib-Identity-Provider", IDP_TEST_EPERSON) + .header("SHIB-MAIL", clarinEperson.getEmail()) + .header(NET_ID_PERSISTENT_ID, NET_ID_TEST_EPERSON)) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("http://localhost:4000")) + .andReturn().getResponse().getHeader("Authorization"); + + checkUserIsSignedIn(token); + EPerson ePerson = checkUserWasCreated(NET_ID_TEST_EPERSON, IDP_TEST_EPERSON, clarinEperson.getEmail(), null); + deleteShibbolethUser(ePerson); + } + + @Test + public void testSuccessFullLoginWithTwoEmails() throws Exception { + String firstEmail = "efg@test.edu"; + String secondEmail = "abc@test.edu"; + String token = getClient().perform(get("/api/authn/shibboleth") + .header("Shib-Identity-Provider", IDP_TEST_EPERSON) + .header("SHIB-MAIL", firstEmail + ";" + secondEmail)) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("http://localhost:4000")) + .andReturn().getResponse().getHeader("Authorization"); + + checkUserIsSignedIn(token); + // Find the user by the second email + EPerson ePerson = checkUserWasCreated(null, IDP_TEST_EPERSON, secondEmail, null); + assertTrue(Objects.nonNull(ePerson)); + deleteShibbolethUser(ePerson); + } + + // The user has changed the email. But that email is already used by another user. + @Test + public void testDuplicateEmailError() throws Exception { + String userWithEppnEmail = "user@eppn.sk"; + String customEppn = "custom eppn"; + + // Create a user with netid and email + String tokenEppnUser = getClient().perform(get("/api/authn/shibboleth") + .header("Shib-Identity-Provider", IDP_TEST_EPERSON) + .header(NET_ID_PERSISTENT_ID, customEppn) + .header("SHIB-MAIL", userWithEppnEmail)) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("http://localhost:4000")) + .andReturn().getResponse().getHeader("Authorization"); + + checkUserIsSignedIn(tokenEppnUser); + + // Try to update an email of existing user - the email is already used by another user - the user should be + // redirected to the login page + getClient().perform(get("/api/authn/shibboleth") + .header("Shib-Identity-Provider", IDP_TEST_EPERSON) + .header(NET_ID_PERSISTENT_ID, NET_ID_TEST_EPERSON) + .header("SHIB-MAIL", userWithEppnEmail)) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("http://localhost:4000/login/duplicate-user?email=" + userWithEppnEmail)); + + // Check if was created a user with such email and netid. + EPerson ePerson = checkUserWasCreated(customEppn, IDP_TEST_EPERSON, userWithEppnEmail, null); + // Delete created eperson - clean after the test + deleteShibbolethUser(ePerson); + } + + // mail=null, eppn=null, persistent-id=somestring + @Test + public void shouldAskForEmailWhenHasPersistentId() throws Exception { + String persistentId = "some pid"; + + // Try to log in a user without email, but with persistent id. The user should be redirected to the page where + // he will fill in the user email. + getClient().perform(get("/api/authn/shibboleth") + .header("Shib-Identity-Provider", IDP_TEST_EPERSON) + .header(NET_ID_PERSISTENT_ID, persistentId)) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("http://localhost:4000/login/auth-failed?netid=" + + Util.formatNetId(persistentId, IDP_TEST_EPERSON))); + } + + // The user was registered and signed in with the verification token on the second attempt, after the email + // containing the verification token was sent. + @Test + public void shouldNotAuthenticateOnSecondAttemptWithoutVerificationTokenInRequest() throws Exception { + String email = "test@mail.epic"; + String netId = email; + String idp = "Test Idp"; + + // Try to authenticate but the Shibboleth doesn't send the email in the header, so the user won't be registered + // but the user will be redirected to the page where he will fill in the user email. + getClient().perform(get("/api/authn/shibboleth") + .header("Shib-Identity-Provider", idp) + .header("SHIB-NETID", netId)) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("http://localhost:4000/login/auth-failed?netid=" + + Util.formatNetId(netId, idp))); + + // Send the email with the verification token. + String tokenAdmin = getAuthToken(admin.getEmail(), password); + getClient(tokenAdmin).perform(post("/api/autoregistration?netid=" + netId + "&email=" + email) + .contentType(MediaType.APPLICATION_JSON_PATCH_JSON)) + .andExpect(status().isOk()); + + // Load the created verification token. + ClarinVerificationToken clarinVerificationToken = clarinVerificationTokenService.findByNetID(context, netId); + assertTrue(Objects.nonNull(clarinVerificationToken)); + + // Try to authenticate the user again, and it should NOT to be automatically registered and signed in, + // because the verification token is not passed in the request header. + getClient().perform(get("/api/authn/shibboleth") + .header("Shib-Identity-Provider", idp) + .header("SHIB-NETID", netId)) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("http://localhost:4000/login/auth-failed?netid=" + + Util.formatNetId(netId, idp))); + } + + @Test + public void shouldSendShibbolethAuthError() throws Exception { + String idp = "Test Idp"; + + // Try to authenticate but the Shibboleth doesn't send the email or netid in the header, + // so the user won't be registered but the user will be redirected to the login page with the error message. + getClient().perform(get("/api/authn/shibboleth") + .header("Shib-Identity-Provider", idp)) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("http://localhost:4000/login?error=shibboleth-authentication-failed")); + } + + private EPerson checkUserWasCreated(String netIdValue, String idpValue, String email, String name) + throws SQLException { + // Check if was created a user with such email and netid. + EPerson ePerson = null; + if (netIdValue != null) { + ePerson = ePersonService.findByNetid(context, Util.formatNetId(netIdValue, idpValue)); + } else { + ePerson = ePersonService.findByEmail(context, email); + } + assertTrue(Objects.nonNull(ePerson)); + if (email != null) { + assertEquals(ePerson.getEmail(), email); + } + + if (name != null) { + assertEquals(ePerson.getFirstName(), name); + } + return ePerson; + } + + private void checkUserIsSignedIn(String token) throws Exception { getClient(token).perform(get("/api/authn/status")) .andExpect(status().isOk()) .andExpect(jsonPath("$.authenticated", is(true))) .andExpect(jsonPath("$.authenticationMethod", is("shibboleth"))); + } - // Check if was created a user with such email and netid. - EPerson ePerson = ePersonService.findByNetid(context, Util.formatNetId(testNetId, testIdp)); - assertTrue(Objects.nonNull(ePerson)); - assertEquals(ePerson.getEmail(), testMail); - assertEquals(ePerson.getFirstName(), "knihovna Kůň test Žluťoučký"); + private void deleteShibbolethUser(EPerson ePerson) throws Exception { EPersonBuilder.deleteEPerson(ePerson.getID()); + // Check it was correctly deleted getClient(token).perform( get("/api/authz/authorizations/search/object") .param("embed", "feature") @@ -528,16 +711,4 @@ public void testUTF8ShibHeaders() throws Exception { .andExpect(jsonPath("$.page.totalElements", is(0))) .andExpect(jsonPath("$._embedded").doesNotExist()); } - - @Test - public void testRedirectToMissingHeadersWithRedirectUrlParam() throws Exception { - String expectedMissingHeadersUrl = configurationService.getProperty("dspace.ui.url") + "/login/missing-headers"; - - getClient().perform(get("/api/authn/shibboleth") - .param("redirectUrl", "http://localhost:8080/server/api/authn/status") - .header("SHIB-MAIL", clarinEperson.getEmail()) - .header("SHIB-NETID", NET_ID_TEST_EPERSON)) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl(expectedMissingHeadersUrl)); - } } diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/utils/UtilsTest.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/utils/UtilsTest.java new file mode 100644 index 000000000000..68c521bfaf06 --- /dev/null +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/utils/UtilsTest.java @@ -0,0 +1,61 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.utils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.dspace.AbstractUnitTest; +import org.junit.Test; + +/** + * Unit tests for {@link Utils} + * + * @author Milan Majchrak (dspace at dataquest.sk) + */ +public class UtilsTest extends AbstractUnitTest { + + @Test + public void testNormalizeDiscoverQueryWithMixedCharactersAndNumbers() { + String searchValue = "my1Search2"; + String searchField = "dc.contributor.author"; + + String expected = "dc.contributor.author:*my* AND dc.contributor.author:*Search* AND " + + "dc.contributor.author:*1* AND dc.contributor.author:*2*"; + + String result = Utils.normalizeDiscoverQuery(searchValue, searchField); + assertEquals(expected, result); + } + + @Test + public void testNormalizeDiscoverQueryWithOnlyCharacters() { + String searchValue = "mySearch"; + String searchField = "dc.contributor.author"; + + String result = Utils.normalizeDiscoverQuery(searchValue, searchField); + assertNull(result); + } + + @Test + public void testNormalizeDiscoverQueryWithOnlyNumbers() { + String searchValue = "12345"; + String searchField = "dc.contributor.author"; + + String result = Utils.normalizeDiscoverQuery(searchValue, searchField); + assertNull(result); + } + + @Test + public void testNormalizeDiscoverQueryWithEmptyString() { + String searchValue = ""; + String searchField = "dc.contributor.author"; + + String result = Utils.normalizeDiscoverQuery(searchValue, searchField); + assertNull(result); + } +} diff --git a/dspace/config/clarin-dspace.cfg b/dspace/config/clarin-dspace.cfg index 6c00f45b5a1e..f27d6830c213 100644 --- a/dspace/config/clarin-dspace.cfg +++ b/dspace/config/clarin-dspace.cfg @@ -5,7 +5,7 @@ #------------------------------------------------------------------# #---------------------------DSpace---------------------------------# #------------------------------------------------------------------# -dspace.name.short = DSpace +dspace.shortname = DSpace dspace.name = CLARIN DSpace #------------------------------------------------------------------# @@ -155,6 +155,7 @@ matomo.site.id = 1 matomo.tracker.bitstream.site_id = 1 matomo.tracker.oai.site_id = 1 matomo.tracker.host.url = http://url:port/matomo.php +matomo.custom.dimension.handle.id = 1 statistics.cache-server.uri = http://cache-server.none #### Statistic usage reports #### @@ -209,13 +210,13 @@ identifier.doi.namespaceseparator = dspace/ ## crosswalk.dissemination.DataCite.stylesheet = crosswalks/DIM2DataCite.xsl crosswalk.dissemination.DataCite.schemaLocation = \ - http://datacite.org/schema/kernel-3 \ - http://schema.datacite.org/meta/kernel-3/metadata.xsd + http://datacite.org/schema/kernel-4 \ + https://schema.datacite.org/meta/kernel-4.5/metadata.xsd crosswalk.dissemination.DataCite.preferList = false crosswalk.dissemination.DataCite.publisher = My University #crosswalk.dissemination.DataCite.dataManager = # defaults to publisher #crosswalk.dissemination.DataCite.hostingInstitution = # defaults to publisher -crosswalk.dissemination.DataCite.namespace = http://datacite.org/schema/kernel-3 +crosswalk.dissemination.DataCite.namespace = http://datacite.org/schema/kernel-4 # consumer to update metadata of DOIs event.consumer.doi.class = org.dspace.identifier.doi.DOIConsumer @@ -288,3 +289,15 @@ elg.download-location.exposed = 0 # left here for reference #download.email.cc = ${info.recipient} download.email.cc = ${mail.admin} + +#### Submission forms #### +# `autocomplete.custom.separator.*` is a special separator used to distinguish values +# in the results from the current Solr index. For example, `solr-subject_ac` is a special index for subjects, +# and its values look like this: `value1 ||| VALUE1`. The correct value is the second one. +# `//` is because of special character +autocomplete.custom.separator.solr-subject_ac = \\|\\|\\| +autocomplete.custom.separator.solr-publisher_ac = \\|\\|\\| +autocomplete.custom.separator.solr-dataProvider_ac = \\|\\|\\| +autocomplete.custom.separator.solr-dctype_ac = \\|\\|\\| +autocomplete.custom.separator.solr-author_ac = \\|\\|\\| +autocomplete.custom.allowed = solr-author_ac,solr-publisher_ac,solr-dataProvider_ac,solr-dctype_ac,solr-subject_ac,solr-handle_title_ac,json_static-iso_langs.json diff --git a/dspace/config/crosswalks/DIM2DataCite.xsl b/dspace/config/crosswalks/DIM2DataCite.xsl index 51fef2275c40..fe6622a1f5fb 100644 --- a/dspace/config/crosswalks/DIM2DataCite.xsl +++ b/dspace/config/crosswalks/DIM2DataCite.xsl @@ -48,7 +48,7 @@ --> + xsi:schemaLocation="http://datacite.org/schema/kernel-4 https://schema.datacite.org/meta/kernel-4.5/metadata.xsd"> - - - - - + + + + diff --git a/dspace/config/crosswalks/oai/metadataFormats/elg.xsl b/dspace/config/crosswalks/oai/metadataFormats/elg.xsl index b822d6e2fad5..33b91afa1ca0 100644 --- a/dspace/config/crosswalks/oai/metadataFormats/elg.xsl +++ b/dspace/config/crosswalks/oai/metadataFormats/elg.xsl @@ -193,13 +193,13 @@ - + - + @@ -216,16 +216,16 @@ - http://w3id.org/meta-share/meta-share/other + http://w3id.org/meta-share/meta-share/other - + Organization - + @@ -381,11 +381,198 @@ elg.xml:62: element typeOfVideoContent: Schemas validity error : Element '{http: + + + en + cs + lt + ur + se + ko + ug + vi + be + tr + sa + sk + uk + ru + ar + fa + de + it + hi + so + el + pl + pt + fr + af + bg + ca + cu + cy + da + et + eu + fo + fa + fi + ga + he + hr + hu + hy + id + is + it + ja + la + lv + mr + mt + nl + nn + nb + ro + sl + sr + ta + te + wo + zh + no + gv + gd + sv + gl + kk + am + br + th + bn + gu + kn + ml + mk + ne + sq + sw + jv + tt + fy + es + sh + tl + pa + ab + ak + an + as + av + ay + az + ba + bm + bi + bo + ch + ce + cv + kw + cr + dv + dz + ep + ee + fj + ff + ht + ha + hz + ho + ig + iu + ie + ia + ik + kl + ks + ka + kr + km + ki + ky + kv + kg + ku + lo + ln + lb + lg + mh + mg + mn + mi + ms + my + na + nv + ng + ny + oc + or + om + os + pi + ps + qu + rm + rn + sg + si + sm + sn + sd + st + sc + ss + su + ty + tg + ti + to + tn + ts + tk + tw + uz + ve + vo + wa + xh + yi + yo + za + zu + bs + co + eo + gn + zu + io + li + mn + bs + rw + + + - + - + @@ -607,7 +794,7 @@ elg.xml:62: element typeOfVideoContent: Schemas validity error : Element '{http: test="doc:metadata/doc:element[@name='local']/doc:element[@name='size']/doc:element[@name='info']/doc:element/doc:field[@name='value']"> - + @@ -616,7 +803,7 @@ elg.xml:62: element typeOfVideoContent: Schemas validity error : Element '{http: - + diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg index c07753e4a9e6..759983b45535 100644 --- a/dspace/config/dspace.cfg +++ b/dspace/config/dspace.cfg @@ -1553,7 +1553,8 @@ request.item.reject.email = true #------------------SUBMISSION CONFIGURATION------------------------# #------------------------------------------------------------------# # Field to use for type binding, default dc.type -submit.type-bind.field = dc.type +# It could be in the form of schema.element.qualifier=>metadata_field, or just metadata_field +submit.type-bind.field = dc.type,dc.language.iso=>edm.type #---------------------------------------------------------------# #----------SOLR DATABASE RESYNC SCRIPT CONFIGURATION------------# diff --git a/dspace/config/emails/clarin_download_link_admin b/dspace/config/emails/clarin_download_link_admin index ecb909bf5d31..3288ea422d9f 100644 --- a/dspace/config/emails/clarin_download_link_admin +++ b/dspace/config/emails/clarin_download_link_admin @@ -9,7 +9,7 @@ ## ## See org.dspace.core.Email for information on the format of this file. ## -#set($subject = "${config.get('dspace.name.short')}: New File Download Request (CC)") +#set($subject = "${config.get('dspace.shortname')}: New File Download Request (CC)") This is an information for administrators and other configured people about a new download request. @@ -26,10 +26,10 @@ The file is distributed under specific license: Extra information filled by the user: ${params[5]} -${config.get('dspace.name.short')} Team +${config.get('dspace.shortname')} Team _____________________________________ ${config.get('dspace.name')}, -WWW: ${config.get('dspace.url')} -Email: ${config.get('help.mail')} -Tel.: ${config.get('help.phone')} +WWW: ${config.get('dspace.ui.url')} +Email: ${config.get('lr.help.mail')} +Tel.: ${config.get('lr.help.phone')} diff --git a/dspace/config/emails/share_submission b/dspace/config/emails/share_submission new file mode 100644 index 000000000000..6a930dee95f3 --- /dev/null +++ b/dspace/config/emails/share_submission @@ -0,0 +1,21 @@ +## E-mail with a download link +## +## Parameters: {0} is expanded to submission link +## +## See org.dspace.core.Email for information on the format of this file. +## +#set($subject = 'Submission share link') +To pass your submission to another user give them the following link: + ${params[0]} + +If you have trouble please contact +${config.get('lr.help.mail')} or call us at ${config.get('lr.help.phone')} + + +${config.get('dspace.shortname')} Team + +_____________________________________ +${config.get('dspace.name')}, +WWW: ${config.get('dspace.ui.url')} +Email: ${config.get('lr.help.mail')} +Tel.: ${config.get('lr.help.phone')} diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index 82e4fd738038..92bd49c8a589 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -28,6 +28,7 @@ org.ehcache.jsr107.EhcacheCachingProvider + false diff --git a/dspace/config/item-submission.xml b/dspace/config/item-submission.xml index cd6d3b66f346..09a4fe3b4176 100644 --- a/dspace/config/item-submission.xml +++ b/dspace/config/item-submission.xml @@ -19,7 +19,6 @@ - + @@ -78,6 +81,7 @@ + diff --git a/dspace/config/submission-forms.xml b/dspace/config/submission-forms.xml index ad6044e3054d..19aa68c31d78 100644 --- a/dspace/config/submission-forms.xml +++ b/dspace/config/submission-forms.xml @@ -53,60 +53,33 @@
- - - local - contact - person - true - - - complex - This is contact person - - - - - - local - sponsor - true - - - complex - This is funding - - - dc - contributor - author + type + true - - clarin-name - Enter the author's name (Family name, Given names). - - + + dropdown + Type of the resource: "Corpus" refers to text, speech and multimodal corpora. + "Lexical Conceptual Resource" includes lexica, ontologies, dictionaries, word lists etc. + "language Description" covers language models and grammars. + "Technology / Tool / Service" is used for tools, systems, system components etc. + + Please select a resource type for your submission. - - dc - title - - false - - onebox - Enter the main title of the item. - You must enter a main title for this item. - + + dc + title + + false + + onebox + Enter the main title of the item in English. + You must enter a main title for this item. + @@ -153,78 +126,123 @@ date issued false - + date - Please give the date of previous publication or public distribution. - You can leave out the day and/or month if they aren't applicable. + Please give the date when the submission data were issued if any e.g., 2014-01-21 or at least + the year. - You must enter at least the year. + You must enter the date or at least the year in a valid format. dc publisher - false + true - onebox - Enter the name of the publisher of the previously issued instance of this item. - + autocomplete + Enter the name of the publisher of the previously issued instance of this item, or your home + institution. Start typing the publisher and use autocomplete form that will appear if + applicable. End your input by pressing ESC if you don't want to use the preselected value. + + You must enter the name of the publisher. - dc - identifier - citation - false - - onebox - Enter the standard citation for the previously issued instance of this item. - + local + hidden + + true + + + list + Indicate whether you want to hide this item from browse and search. Combine with "Upload cmdi" + for weblicht submissions. + + + + policy=deny,action=read,grantee-type=user,grantee-id=* + - dc - relation - ispartofseries + local + hasCMDI true - - Technical Report - series - Enter the series and number assigned to this item by your community. + + list + Indicate whether you will upload cmdi file in the next step. Combine with "hide" for weblicht + submissions. + + + policy=deny,action=read,grantee-type=user,grantee-id=* + dc - identifier - - + contributor + author true - - qualdrop_value - If the item has any identification numbers or codes associated with - it, please enter the types and the actual numbers or codes. + + autocomplete + Enter the names of the authors of this item. Start typing the author's last name and use + autocomplete form that will appear if applicable. End your input by pressing ESC if you don't + want to use the preselected value. + Please add author(s) + + + + + + local + contact + person + true + + + complex + Person to contact in case of any issues with this submission. + Please fill all the fields for the contact person. + + + + + local + sponsor + true + + + complex + Acknowledge sponsors and funding that supported work described by this submission. +
+ +
dc - type + description - true - - dropdown - Select the type of content of the item. - - + false + + textarea + Enter a description of the submitted data. + Please give us a description @@ -235,10 +253,9 @@ false corpus,lexicalConceptualResource,languageDescription - dropdown - Select the language of the main content of the item. If the language does not appear in the - list, please select 'Other'. If the content does not really have a language (for example, if it - is a dataset or an image) please select 'N/A'. + autocomplete + Select the language of the main content of the item. Multiple languages are possible. Start + typing the language and use autocomplete form that will appear if applicable. Better to list all the languages then to use the 'mul' iso code (if there are too many, contact support). Please choose a language for the resource. @@ -251,11 +268,31 @@ true toolService - onebox - If the tool/service is language dependent, select the appropriate language(s). Otherwise leave the field empty. Multiple languages are possible. Start typing the language and use autocomplete form that will appear. + autocomplete + If the tool/service is language dependent, select the appropriate language(s). Otherwise leave the field empty. Multiple languages are possible. Start typing the language and use autocomplete + form that will appear. + + + + dc + subject + + + true + + tag + Enter appropriate subject keyword or phrase and press the Add button. You can repeat it for + multiple keywords or use separators i.e., comma and semicolon, which will split it accordingly. + Start typing the keyword and use autocomplete form that will appear. End your input by pressing + ESC if you don't want to use the preselected value. + + Please enter at least one subject related to your submission + srsc + + local @@ -265,9 +302,11 @@ corpus,languageDescription,lexicalConceptualResource complex - You can state the extent of the submitted data, eg. the number of tokens. + You can state the extent of the submitted data, e.g., the number of tokens. + + metashare @@ -298,8 +337,6 @@ Media type is required - - metashare @@ -354,49 +391,20 @@ Please indicate whether the tool is language dependent - - - local - hasCMDI - true - - list - Are you going to upload cmdi file? - - - policy=deny,action=read,grantee-type=user,grantee-id=* - - - - - - local - hidden - - true - - - list - Should item be harvestable thru OAI-PMH but behave like private? - - - policy=deny,action=read,grantee-type=user,grantee-id=* - - - local bitstream redirectToURL false - + onebox - The actual maximum upload size of the file is 4GB. To upload a file bigger than the - maximum upload size, enter the URL of that large file. The admin must know the URL - of that bitstream file. Then, click on the 'Save' button, and the file will start - to upload. The file path must be an absolute path. + Please enter the full path of the file, either from the local server or an HTTP location. + The maximum upload size for a file is 4GB. To upload a file larger than this limit, provide the + URL of the file. Ensure the admin is aware of the URL of the bitstream file. + Once the file path or URL is entered, click the 'Save' button to begin the upload. + Note: The file path must be an absolute path. @@ -408,62 +416,6 @@ - - - - dc - subject - - - true - - tag - Enter appropriate subject keyword or phrase and press the Add button. You can repeat it for - multiple keywords or use separators i.e., Enter and comma, which will split it accordingly. - Start typing the keyword and use autocomplete form that will appear. End your input by pressing - ESC if you don't want to use the preselected value. - - Please enter at least one subject related to your submission - srsc - - - - - dc - description - abstract - false - - textarea - Enter the abstract of the item. - - - - - - dc - description - sponsorship - false - - textarea - Enter the names of any sponsors and/or funding codes in the box. - - - - - - dc - description - - false - - textarea - Enter any other description or comments in this box. - - - - Indicate whether you will upload cmdi file in the next step. Combine with "hide" for weblicht @@ -1703,7 +1655,7 @@ true - autocomplete + autocomplete The name of the publisher of the original analog or born digital object. Use your home institution if this is a born digital object being published now. Start typing the publisher and use autocomplete form that will appear if applicable. End your input by pressing ESC if you @@ -1719,9 +1671,9 @@ false - autocomplete + autocomplete This concerns the digital object (not the analog - original). An institution from which the data come. Used eg. to give proper attribution. Generally + original). An institution from which the data come. Used e.g., to give proper attribution. Generally different from publisher. @@ -1757,7 +1709,7 @@ false - autocomplete + autocomplete The type should be different from what you have entered in the first step. Examples: photo or painting for IMAGE, book or letter for TEXT, etc. Type is required @@ -1776,6 +1728,9 @@ Please give us a description
+ dc @@ -1783,8 +1738,8 @@ iso true - TEXT - autocomplete + TEXT + autocomplete Select the language of the main content of the item. Multiple languages are possible. Start typing the language and use autocomplete form that will appear if applicable. Better to list all the languages then to use the 'mul' iso code (if there are too many, contact support). @@ -1798,8 +1753,8 @@ iso true - VIDEO,IMAGE,SOUND,3D - autocomplete + VIDEO,IMAGE,SOUND,3D + autocomplete Optionally, select the language of the main content of the item. Multiple languages are possible. Start typing the language and use autocomplete form that will appear if applicable. Better to list all the languages then to use the 'mul' iso code (if there are too many, contact support). @@ -1815,7 +1770,7 @@ true - tag + autocomplete Enter appropriate subject keyword or phrase and press the Add button. Use keywords to specify also people, places and times (period, era, date range etc) the resource is about. You can use hierarchical subjects, separate the hierarchy levels with two colons (::). Eg. @@ -1854,6 +1809,27 @@ You can state the extent of the submitted data, eg. the number of tokens. + + + local + bitstream + redirectToURL + false + + onebox + + Please enter the full path of the file, either from the local server or an HTTP location. + The maximum upload size for a file is 4GB. To upload a file larger than this limit, provide the + URL of the file. Ensure the admin is aware of the URL of the bitstream file. + Once the file path or URL is entered, click the 'Save' button to begin the upload. + Note: The file path must be an absolute path. + + + + policy=deny,action=read,grantee-type=user,grantee-id=* + + +
@@ -1922,7 +1898,7 @@ hidden false - + list Indicate whether you want to hide this item from browse and search. Combine with "Upload cmdi" for weblicht submissions. @@ -1939,7 +1915,7 @@ hasMetadata false - + list Indicate whether you will upload cmdi file in the next step. Combine with "hide" for weblicht @@ -1982,7 +1958,7 @@ true - autocomplete + autocomplete The name of the publisher of the original analog or born digital object. Use your home institution if this is a born digital object being published now. Start typing the publisher and use autocomplete form that will appear if applicable. End your input by pressing ESC if you @@ -2049,7 +2025,7 @@ iso true - autocomplete + autocomplete Select the language of the main content of the item. Multiple languages are possible. Start typing the language and use autocomplete form that will appear if applicable. Better to list all the languages then to use the 'mul' iso code (if there are too many, contact support). @@ -2064,7 +2040,7 @@ true - autocomplete + autocomplete Enter appropriate subject keyword or phrase and press the Add button. You can repeat it for multiple keywords or use separators i.e., comma and semicolon, which will split it accordingly. Start typing the keyword and use autocomplete form that will appear. End your input by pressing @@ -2089,6 +2065,27 @@ + + + local + bitstream + redirectToURL + false + + onebox + + Please enter the full path of the file, either from the local server or an HTTP location. + The maximum upload size for a file is 4GB. To upload a file larger than this limit, provide the + URL of the file. Ensure the admin is aware of the URL of the bitstream file. + Once the file path or URL is entered, click the 'Save' button to begin the upload. + Note: The file path must be an absolute path. + + + + policy=deny,action=read,grantee-type=user,grantee-id=* + + +
@@ -2113,8 +2110,11 @@ replaces true - onebox - URL to a related resource that is supplanted, displaced, or superseded by the described resource. If the replaced resource is in this repository start typing its name or handle and select the resource from the autocomplete popup. + autocomplete + URL to a related resource that is supplanted, displaced, or superseded by the described + resource. If the replaced resource is in this repository start typing its name or handle and select the + resource from the autocomplete popup. + @@ -2126,8 +2126,8 @@ relation isreplacedby true - - onebox + + autocomplete A related resource that supplants, displaces, or supersedes the described resource. @@ -3201,7 +3201,7 @@ - Hidden + Yes hidden @@ -3219,15 +3219,15 @@ Uncomment the example row of the complex input type definition to see this input in the submission UI. --> - - - - + + + + - + @@ -3240,8 +3240,8 @@ - - + \ No newline at end of file diff --git a/dspace/config/submission-forms_cs.xml b/dspace/config/submission-forms_cs.xml index c3250c3a357a..c81cff0133b1 100644 --- a/dspace/config/submission-forms_cs.xml +++ b/dspace/config/submission-forms_cs.xml @@ -29,10 +29,10 @@ title false - + onebox - Enter the name of the file. - You must enter a name for this file + Zadejte název souboru. + Musíte zadat název tohoto souboru. @@ -40,9 +40,9 @@ dc description true - + textarea - Enter a description for the file + Zadejte popis souboru. @@ -55,69 +55,69 @@ Uncomment it and the complex input will be showed in the submission UI. --> - local - contact - person + dc + type + true - - - complex - This is contact person - + + dropdown + "Corpus" označuje textové, řečové i multimodální korpusy. + "Lexical Conceptual Resource" zahrnuje lexikony, ontologie, slovníky, seznamy slov apod. + "Language Description" zahrnuje jazykové modely a gramatiky. + "Technology / Tool / Service" se používá pro nástroje, systémy, systémové komponenty atd. + + Prosím zvolte typ dat pro váš příspěvek. - local - sponsor - true - - - complex - This is funding - + dc + title + + false + + onebox + Uveďte anglický název tohoto příspěvku. + Musíte uvést název. dc - contributor - author - true - - clarin-name - Enter the author's name (Family name, Given names). - - + source + uri + false + + onebox + Uveďte URL projektu + + http.* - dc - title - + local + demo + uri false - + onebox - Uveďte anglický název tohoto příspěvku. - Musíte uvést název. + URL se vzorky dat, v případě nástrojů předvedení výstupu. + + http.* dc - title - alternative + relation + isreferencedby true - + onebox - If the item has any alternative titles, please enter them here. - + Odkaz na původní článek, který zmiňuje tento záznam. + + http.* @@ -126,78 +126,111 @@ date issued false - + date - Please give the date of previous publication or public distribution. - You can leave out the day and/or month if they aren't applicable. - - You must enter at least the year. + Uveďte prosím datum vydání příspěvku, např 2014-01-21 nebo alespoň rok. + Musíte uvést datum v platném formátu. dc publisher - false - + true + - onebox - Enter the name of the publisher of the previously issued instance of this item. - + autocomplete + Uveďte vydavatele předchozího vydání, nebo vaši domovskou instituci. Začnete-li vyplňovat vydavatele, objeví se nápověda. Nechcete-li nápovědu využít, stiskněte ESC. + Musíte uvést vydavatele. - dc - identifier - citation - false - - onebox - Enter the standard citation for the previously issued instance of this item. - + local + hidden + + true + + + list + Uveďte, má-li být záznam skryt ve vyhledávání a procházení. Pro příspěvky pro weblicht kombinujte s "Nahrát cmdi". + + + policy=deny,action=read,grantee-type=user,grantee-id=* + - dc - relation - ispartofseries + local + hasCMDI true - - Technical Report - series - Enter the series and number assigned to this item by your community. + + list + Uveďte, jestli se chystáte v dalším kroku nahrát cmdi soubor. Kombinujte se schováváním záznamů pro weblicht příspěvky. + + policy=deny,action=read,grantee-type=user,grantee-id=* + dc - identifier - - + contributor + author true - - qualdrop_value - If the item has any identification numbers or codes associated with - it, please enter the types and the actual numbers or codes. - + + autocomplete + Uveďte jména autorů tohoto záznamu. Začnete-li vyplňovat příjmení, objeví se nápověda. Nechcete-li nápovědu využít, stiskněte ESC. + Uveďte prosím autora(y) + + + + + + local + contact + person + true + + + complex + Osoba, která bude kontaktována v případě problémů s tímto záznamem. + Vyplňte prosím všechna pole u kontaktní osoby + + + + + local + sponsor + true + + + complex + Uveďte sponzory a zdroje financí podporující vznik práce popsané v tomto příspěvku. + + +
dc - type + description - true - - dropdown - Select the type of content of the item. - - + false + + textarea + Popište nahrávaná data. + Uveďte prosím popis. @@ -206,121 +239,158 @@ language iso false - - dropdown - Select the language of the main content of the item. If the language does not appear in the - list, please select 'Other'. If the content does not really have a language (for example, if it - is a dataset or an image) please select 'N/A'. - - + + corpus,lexicalConceptualResource,languageDescription + autocomplete + Vyberte jazyky, jichž se data tohoto záznamu týkají. Je možné zvolit více jazyků. Začnete-li psát, objeví se nápověda. Je lepší vyjmenovat všechny dotčené jazyky (pokud jich je větší množství, kontaktujte podporu), než používat iso kód 'mul'. + Prosím zvolte jazyk. - local - hasCMDI + dc + language + iso true - - list - Are you going to upload cmdi file? - - - policy=deny,action=read,grantee-type=user,grantee-id=* - + + toolService + autocomplete + Pokud je nástroj/služba jazykově závislá, uveďte potřebné jazyky. Jinak můžete nechat nevyplněné. Je možné zvolit více jazyků. Začnete-li psát, objeví se nápověda. + - local - hidden - + dc + subject + + true - - - list - Should item be harvestable thru OAI-PMH but behave like private? - - - policy=deny,action=read,grantee-type=user,grantee-id=* - + + tag + Uveďte vhodná klíčová slova, nebo fráze a zmáčkněte tlačítko přidat. + Klíčová slova buď přidávejte po jednom, nebo je oddělte čárkou, nebo středníkem. Začnete-li psát, objeví se nápověda. + + Uveďte alespoň jedno klíčové slovo. + srsc local - bitstream - redirectToURL - false - - onebox - - The actual maximum upload size of the file is 4GB. To upload the file bigger than maximum - upload size type the URL of that big file. Admin must know URL to that bitstream file. - Then click on the 'Save' button and the file will start to upload. The file will be loaded - from the '/temp' folder of the server. Example: /tomcat/temp/bitstream.png - - - - policy=deny,action=read,grantee-type=user,grantee-id=* - + size + info + true + + corpus,languageDescription,lexicalConceptualResource + complex + Můžete uvést rozsah nahraných dat, například počet tokenů. -
- -
+ - dc - subject - - - true - - tag - Enter appropriate subject keywords or phrases. - Please enter at least one subject related to your submission - srsc + metashare + ResourceInfo#ContentInfo + mediaType + false + + corpus,lexicalConceptualResource + dropdown + Zvolte druh média tohoto záznamu, např. "text" pro textový korpus, "audio" pro audio nahrávky. + Uveďte typ média - dc - description - abstract + metashare + ResourceInfo#ContentInfo + mediaType false - - textarea - Enter the abstract of the item. - + + languageDescription + dropdown + Zvolte druh média tohoto záznamu, např. "text" pro textový korpus, "audio" pro audio nahrávky. + Uveďte typ média - dc - description - sponsorship + metashare + ResourceInfo#ContentInfo + detailedType false - - textarea - Enter the names of any sponsors and/or funding codes in the box. - + + toolService + dropdown + + Zvolte jeden z podtypů - dc - description - + metashare + ResourceInfo#ContentInfo + detailedType false - - textarea - Enter any other description or comments in this box. + + languageDescription + dropdown + + Zvolte jeden z podtypů + + + + + metashare + ResourceInfo#ContentInfo + detailedType + false + + lexicalConceptualResource + dropdown + + Zvolte jeden z podtypů + + + + + metashare + ResourceInfo#ResourceComponentType#ToolServiceInfo + languageDependent + false + + toolService + list + Uveďte zda funkce nástroje či služby závisí na konkrétním jazyku. + Uveďte prosím zda je nástroj jazykově závislý. + + + + + local + bitstream + redirectToURL + false + + onebox + + Prosím, zadejte úplnou cestu k souboru, ať už z místního serveru, nebo z webové lokace (HTTP). + Maximální velikost nahrávaného souboru je 4 GB. Pokud chcete nahrát soubor větší než tento limit, zadejte jeho URL. + Ujistěte se, že administrátor je obeznámen s URL souboru datového proudu (bitstream). + Jakmile zadáte cestu k souboru nebo URL, klikněte na tlačítko „Uložit“ pro zahájení nahrávání. + Poznámka: Cesta k souboru musí být absolutní. + + + policy=deny,action=read,grantee-type=user,grantee-id=* +
+ - - - - - - - - - + + + + + + + + + + person @@ -568,36 +638,36 @@ Enter the id of the project - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + isProjectOfPerson @@ -1432,45 +1502,293 @@ -
+ - local - submission - note - false - - textarea - Zde můžete zanechat vzkaz editorům. - + edm + type + + false + + dropdown + Vyberte z TEXT, VIDEO, ZVUK, OBRAZ, 3D. Pokud zvolíte TEXT, + zvažte, jestli se nejedná o Language Resource (jazykový zdroj). Obrazy jsou vizuální materiály, na které se + uživatelé mohou dívat. Textové materiály jsou určeny ke čtení a nikoli k dívání. + Zvolte prosím jednu z možností + + + + + dc + title + + false + + onebox + Uveďte anglický název tohoto příspěvku. + Musíte uvést název. + + + + + local + demo + uri + false + + onebox + URL se vzorky dat, v případě nástrojů předvedení výstupu. + + http.* - - dc relation - replaces + isreferencedby true - + onebox - URL příbuzného záznamu, který je tímto záznamem nahrazen. Pokud je příbuzný záznam v tomto repozitáři, začněte psát jeho název, nebo handle a vyberte záznam z nabídky. + Odkaz na původní článek, který zmiňuje tento záznam. + http.* - - dc - relation - isreplacedby + date + issued + false + + date + Uveďte prosím datum vydání příspěvku, např 2014-01-21 nebo alespoň rok. + Musíte uvést datum v platném formátu. + + + + + local + hidden + + false + + list + Uveďte, má-li být záznam skryt ve vyhledávání a procházení. Pro příspěvky pro weblicht kombinujte s "Nahrát cmdi". + + + policy=deny,action=read,grantee-type=user,grantee-id=* + + + + + + local + hasMetadata + + false + + list + + Uveďte, jestli se chystáte v dalším kroku nahrát cmdi soubor. Kombinujte se schováváním záznamů pro weblicht příspěvky. + + + policy=deny,action=read,grantee-type=user,grantee-id=* + + + +
+
+ + + dc + contributor + author + true + + clarin-name + Uveďte jména autorů tohoto záznamu. Začnete-li vyplňovat příjmení, objeví se nápověda. Nechcete-li nápovědu využít, stiskněte ESC. + Uveďte prosím autora(y) + + + + + dc + publisher + + true + + autocomplete + Uveďte vydavatele analogového originálu, případně + vydavatele "born digital" originálu. Pokud se jedná o právě zrozený zdroj, uveďte vaši domovskou instituci. Začnete-li + vyplňovat vydavatele, objeví se nápověda. Nechcete-li nápovědu využít, stiskněte ESC. + Musíte uvést vydavatele. + + + + + local + dataProvider + + false + + autocomplete + Týká se digitálního objektu, nikoliv analogového + originálu. Instituce, od které data pocházejí. Např. pro uvedení původu u některých licencí. Obecně se bude + lišit od vydavatele. + + + + + + local + contact + person + true + + complex + Osoba, která bude kontaktována v případě problémů s tímto záznamem. + Vyplňte prosím všechna pole u kontaktní osoby + + + + + local + sponsor + true + + complex + Uveďte sponzory a zdroje financí podporující vznik práce popsané v tomto příspěvku. + + +
+
+ + + dc + type + + false + + autocomplete + Typ by měl být odlišný od druhu, který jste zvolili v + prvním kroku. Například: fotografie nebo malba pro druh OBRAZ, kniha nebo dopis pro typ TEXT apod. + Vyplňte typ + + + + + + dc + description + + false + + textarea + Popište nahrávaná data. + Uveďte prosím popis. + + + + + + dc + language + iso + true + + TEXT + autocomplete + + Vyberte jazyky, jichž se data tohoto záznamu týkají. Je možné zvolit více jazyků. Začnete-li psát, + objeví se nápověda. Je lepší vyjmenovat všechny dotčené jazyky (pokud jich je větší množství, kontaktujte podporu), + než používat iso kód 'mul'. + + Pro TEXTy je jazyk povinný + + + + + dc + language + iso + true + + VIDEO,IMAGE,SOUND,3D + autocomplete + + Volitelné. Vyberte jazyky, jichž se data tohoto záznamu týkají. Je možné zvolit více jazyků. Začnete-li psát, + objeví se nápověda. Je lepší vyjmenovat všechny dotčené jazyky (pokud jich je větší množství, kontaktujte podporu), + než používat iso kód 'mul'. + + + + + + + dc + subject + + + true + + autocomplete + Uveďte vhodná klíčová slova, nebo fráze a zmáčkněte + tlačítko přidat. + Klíčová slova využijte také pro lidi, místa a časy (období, éry, rozsah dat apod.) o kterých záznam je. + Možnost využítvat hierarchické předměty. Oddělte jednotlivé úrovně hierarchie dvěma dvojtečkami (::). + Např. People::Jára Cimrman, Places::Liptákov, Times::počátek 20. století. + Klíčová slova buď přidávejte po jednom, nebo je oddělte čárkou, nebo středníkem. Začnete-li psát, objeví se nápověda. + + Uveďte alespoň jedno klíčové slovo. + + + + + dc + identifier + other true - + + onebox - Příbuzný záznam, který nahrazuje tento. + Pro tento záznam bude vytvořen handle. Pokud má + zdroj přidělený jiný identifikátor, nebo kód, uveďte jej i jeho typ. + + + + + + local + size + info + true + + complex + Můžete uvést rozsah nahraných dat, například počet tokenů. + + + + + local + bitstream + redirectToURL + false + + onebox + + Prosím, zadejte úplnou cestu k souboru, ať už z místního serveru, nebo z webové lokace (HTTP). + Maximální velikost nahrávaného souboru je 4 GB. Pokud chcete nahrát soubor větší než tento limit, zadejte jeho URL. + Ujistěte se, že administrátor je obeznámen s URL souboru datového proudu (bitstream). + Jakmile zadáte cestu k souboru nebo URL, klikněte na tlačítko „Uložit“ pro zahájení nahrávání. + Poznámka: Cesta k souboru musí být absolutní. + + policy=deny,action=read,grantee-type=user,grantee-id=* @@ -1478,699 +1796,1405 @@
- +
+ + + + dc + title + + false + + onebox + Uveďte anglický název tohoto příspěvku. + Musíte uvést název. + + + + + local + demo + uri + false + + onebox + Stránky kurzu + + http.* + + + + + dc + relation + isreferencedby + true + + onebox + Odkaz na původní článek, který zmiňuje tento záznam. + + http.* + + + + + dc + date + issued + false + + date + Uveďte prosím datum vydání příspěvku, např 2014-01-21 nebo alespoň rok. + Musíte uvést datum v platném formátu. + + + + + local + hidden + + false + + list + Uveďte, má-li být záznam skryt ve vyhledávání a procházení. Pro příspěvky pro weblicht kombinujte s "Nahrát cmdi". + + + policy=deny,action=read,grantee-type=user,grantee-id=* + + + + + + local + hasMetadata + + false + + list + + Uveďte, jestli se chystáte v dalším kroku nahrát cmdi soubor. Kombinujte se schováváním záznamů pro weblicht příspěvky. + + + policy=deny,action=read,grantee-type=user,grantee-id=* + + + +
- - - - - - - - - - +
+ + + + dc + contributor + author + true + + clarin-name + Uveďte jména autorů tohoto záznamu. Začnete-li vyplňovat příjmení, objeví se nápověda. Nechcete-li nápovědu využít, stiskněte ESC. + Uveďte prosím autora(y) + + + + + dc + publisher + + true + + autocomplete + Uveďte vydavatele analogového originálu, případně + vydavatele "born digital" originálu. Pokud se jedná o právě zrozený zdroj, uveďte vaši domovskou instituci. Začnete-li + vyplňovat vydavatele, objeví se nápověda. Nechcete-li nápovědu využít, stiskněte ESC. + Musíte uvést vydavatele. + + + + + local + contact + person + true + + complex + Osoba, která bude kontaktována v případě problémů s tímto záznamem. + Vyplňte prosím všechna pole u kontaktní osoby + + + + + local + sponsor + true + + complex + Uveďte sponzory a zdroje financí podporující vznik práce popsané v tomto příspěvku. + + +
+ +
+ + + dc + type + + false + + + dropdown + teachingMaterials + Toto pole je zde pro automatické vyplnění hodnoty. Hodnota by neměla být měněna. + Prosím zvolte typ dat pro váš příspěvek. + + + + + dc + description + + false + + textarea + Popište nahrávaná data. + Uveďte prosím popis. + + + + + dc + language + iso + true + + autocomplete + + Vyberte jazyky, jichž se data tohoto záznamu týkají. Je možné zvolit více jazyků. Začnete-li psát, objeví se nápověda. + Je lepší vyjmenovat všechny dotčené jazyky (pokud jich je větší množství, kontaktujte podporu), než používat iso kód 'mul'. + + Prosím zvolte jazyk. + + + + + dc + subject + + + true + + autocomplete + + Uveďte vhodná klíčová slova, nebo fráze a zmáčkněte tlačítko přidat. + Klíčová slova buď přidávejte po jednom, nebo je oddělte čárkou, nebo středníkem. Začnete-li psát, objeví se nápověda. + + Uveďte alespoň jedno klíčové slovo. + + + + + dc + identifier + other + true + + + onebox + + Pro tento záznam bude vytvořen handle. Pokud má + zdroj přidělený jiný identifikátor, nebo kód, uveďte jej i jeho typ. + + + + + + + local + bitstream + redirectToURL + false + + onebox + + Prosím, zadejte úplnou cestu k souboru, ať už z místního serveru, nebo z webové lokace (HTTP). + Maximální velikost nahrávaného souboru je 4 GB. Pokud chcete nahrát soubor větší než tento limit, zadejte jeho URL. + Ujistěte se, že administrátor je obeznámen s URL souboru datového proudu (bitstream). + Jakmile zadáte cestu k souboru nebo URL, klikněte na tlačítko „Uložit“ pro zahájení nahrávání. + Poznámka: Cesta k souboru musí být absolutní. + + + + policy=deny,action=read,grantee-type=user,grantee-id=* + + + +
+ +
+ + + local + submission + note + false + + textarea + Zde můžete zanechat vzkaz editorům. + + + + + + + + dc + relation + replaces + true + + autocomplete + + URL příbuzného záznamu, který je tímto záznamem nahrazen. + Pokud je příbuzný záznam v tomto repozitáři, začněte psát jeho název, nebo handle a vyberte záznam z nabídky. + + + + + + + + + dc + relation + isreplacedby + true + + autocomplete + Příbuzný záznam, který nahrazuje tento. + + + policy=deny,action=read,grantee-type=user,grantee-id=* + + + +
+ + + + + + + + + + + + + + - Yes - true + Ano + true + + + + + ISSN + issn + + + Other + other + + + ISMN + ismn + + + Gov't Doc # + govdoc + + + URI + uri + + + ISBN + isbn + + + + + + + N/A + N/A + + + EU + euFunds + + + Own funds + ownFunds + + + National + nationalFunds + + + other + Other + + + + + + N/A + + + + Corpus + corpus + + + Lexical conceptual + lexicalConceptualResource + + + Language description + languageDescription + + + Technology / Tool / Service + toolService + + + + + + + + + N/A + + + + English (United States) + en_US + + + English + en + + + Spanish + es + + + German + de + + + French + fr + + + Italian + it + + + Japanese + ja + + + Chinese + zh + + + Portuguese + pt + + + Turkish + tr + + + (Other) + other + + + + + + + + + + + Without License + + + + Attribution (CC-BY) + http://creativecommons.org/licenses/by/4.0/ + + + Attribution, No Derivative Works (CC-BY-ND) + http://creativecommons.org/licenses/by-nd/4.0/ + + + Attribution, Share-alike (CC-BY-SA) + http://creativecommons.org/licenses/by-sa/4.0/ + + + Attribution, Non-commercial (CC-BY-NC) + http://creativecommons.org/licenses/by-nc/4.0/ + + + Attribution, Non-commercial, No Derivative Works (CC-BY-NC-ND) + http://creativecommons.org/licenses/by-nc-nd/4.0/ + + + Attribution, Non-commercial, Share-alike (CC-BY-NC-SA) + http://creativecommons.org/licenses/by-nc-sa/4.0/ + + + + Other + other + + + + + + + + Interactive Resource + interactive resource + + + - Website + website + + + Dataset + dataset + + + - Interview + interview + + + Image + image + + + - Moving Image + moving image + + + -- Video + video + + + - Still Image + still image + + + Other + other + + + Software + software + + + - Research Software + research software + + + Workflow + workflow + + + Cartographic Material + cartographic material + + + - Map + map + + + Sound + sound + + + - Musical Composition + musical composition + + + Text + text + + + - Annotation + annotation + + + - Bibliography + bibliography + + + - Book + book + + + -- Book Part + book part + + + - Conference Object + conference object + + + -- Conference Proceedings + conference proceedings + + + --- Conference Paper + conference paper + + + --- Conference Poster + conference poster + + + -- Conference Paper Not In Proceedings + conference paper not in proceedings + + + -- Conference Poster Not In Proceedings + conference poster not in proceedings + + + - Lecture + lecture + + + - Letter + letter + + + - Periodical + periodical + + + -- Journal + journal + + + --- Contribution to Journal + contribution to journal + + + ---- Journal Article + journal article + + + ----- Data Paper + data paper + + + ----- Review Article + review article + + + ----- Research Article + research article - - - ISSN - issn + ----- Corrigendum + corrigendum - Other - other + ----- Software Paper + software paper - ISMN - ismn + ---- Editorial + editorial - Gov't Doc # - govdoc + ---- Letter to the Editor + letter to the editor - URI - uri + -- Newspaper + newspaper - ISBN - isbn + --- Newspaper Article + newspaper article - - - - - N/A - N/A + -- Magazine + magazine - EU - euFunds + - Patent + patent - Own funds - ownFunds + - Preprint + preprint - National - nationalFunds + - Report + report - other - Other + -- Report Part + report part + + + -- Internal Report + internal report + + + -- Memorandum + memorandum - - - - Animation - Animation + -- Other Type of Report + other type of report - Article - Article + -- Policy Report + policy report - Book - Book + -- Project Deliverable + project deliverable - Book chapter - Book chapter + --- Data Management Plan + data management plan - Dataset - Dataset + -- Report to Funding Agency + report to funding agency - Learning Object - Learning Object + -- Research Report + research report - Image - Image + -- Technical Report + technical report - Image, 3-D - Image, 3-D + - Research Proposal + research proposal - Map - Map + - Review + review - Musical Score - Musical Score + -- Book Review + book review - Plan or blueprint - Plan or blueprint + - Technical Documentation + technical documentation - Preprint - Preprint + - Working Paper + working paper - Presentation - Presentation + - Thesis + thesis - Recording, acoustical - Recording, acoustical + -- Bachelor Thesis + bachelor thesis - Recording, musical - Recording, musical + -- Doctoral Thesis + doctoral thesis - Recording, oral - Recording, oral + -- Master Thesis + master thesis - Software - Software + - Musical Notation + musical notation - Technical Report - Technical Report + - Blog Post + blog post - Thesis - Thesis + - Manuscript + website - Video - Video + Learning Object + learning object - Working Paper - Working Paper + Clinical Trial + clinical trial - Other - Other + Clinical Study + clinical study - - + - N/A - + Author’s Original + http://purl.org/coar/version/c_b1a7d7d4d402bcce - English (United States) - en_US + Submitted Manuscript Under Review + http://purl.org/coar/version/c_71e4c1898caa6e32 - English - en + Accepted Manuscript + http://purl.org/coar/version/c_ab4af688f83e57aa - Spanish - es + Proof + http://purl.org/coar/version/c_fa2ee174bc00049f - German - de + Version of Record + http://purl.org/coar/version/c_970fb48d4fbd8a85 - French - fr + Corrected Version of Record + http://purl.org/coar/version/c_e19f295774971610 - Italian - it + Enhanced Version of Record + http://purl.org/coar/version/c_dc82b40f9837b551 - Japanese - ja + Not Applicable (or Unknown) + http://purl.org/coar/version/c_be7fb7dd8ff6fe43 + + + - Chinese - zh + open access + http://purl.org/coar/access_right/c_abf2 - Portuguese - pt + embargoed access + http://purl.org/coar/access_right/c_f1cf - Turkish - tr + restricted access + http://purl.org/coar/access_right/c_16ec - (Other) - other + metadata only access + http://purl.org/coar/access_right/c_14cb - - - - - - + - Without License - + Scopus Author ID + scopus-author-id - Attribution (CC-BY) - http://creativecommons.org/licenses/by/4.0/ + Ciencia ID + ciencia-id - Attribution, No Derivative Works (CC-BY-ND) - http://creativecommons.org/licenses/by-nd/4.0/ + Google Scholar ID + gsid - Attribution, Share-alike (CC-BY-SA) - http://creativecommons.org/licenses/by-sa/4.0/ + Open Researcher and Contributor ID (ORCID) + orcid - Attribution, Non-commercial (CC-BY-NC) - http://creativecommons.org/licenses/by-nc/4.0/ + Web of Science ResearcherID + rid - Attribution, Non-commercial, No Derivative Works (CC-BY-NC-ND) - http://creativecommons.org/licenses/by-nc-nd/4.0/ + ISNI - International Standard Name Identifier + isni - Attribution, Non-commercial, Share-alike (CC-BY-NC-SA) - http://creativecommons.org/licenses/by-nc-sa/4.0/ + Other + + + + + + + ISNI - International Standard Name Identifier + isni + + + Ringgold identifier + rin + + + Research Organization Registry + ror - Other - other + + + + + N/A + + + + Is a Funding Organization + FundingOrganization + - - + + + TEXT + TEXT + + + VIDEO + VIDEO + + + ZVUK + SOUND + + + OBRAZ + IMAGE + + + 3D + 3D + + + + + + + + + + N/A + + - Interactive Resource - interactive resource + Web Executable + webExecutable - - Website - website + Paper copy + paperCopy - Dataset - dataset + HardDisk + hardDisk - - Interview - interview + Blu Ray + bluRay - Image - image + DVD-R + DVD-R - - Moving Image - moving image + CD-ROM + CD-ROM - -- Video - video + Download + downloadable - - Still Image - still image + Accessible Through Interface + accessibleThroughInterface - Other + other other + + + + - Software - software + N/A + - - Research Software - research software + True + True - Workflow - workflow + False + False + + + + - Cartographic Material - cartographic material + N/A + - - Map - map + text + text - Sound - sound + audio + audio - - Musical Composition - musical composition + video + video - Text + image + image + + + + + + N/A + + + + text text - - Annotation - annotation + video + video - - Bibliography - bibliography + image + image + + + + - - Book - book + N/A + - -- Book Part - book part + tool + tool - - Conference Object - conference object + service + service - -- Conference Proceedings - conference proceedings + platform + platform - --- Conference Paper - conference paper + suiteOfTools + suiteOfTools - --- Conference Poster - conference poster + infrastructure + infrastructure - -- Conference Paper Not In Proceedings - conference paper not in proceedings + architecture + architecture - -- Conference Poster Not In Proceedings - conference poster not in proceedings + nlpDevelopmentEnvironment + nlpDevelopmentEnvironment - - Lecture - lecture + other + other + + - - Letter - letter + N/A + - - Periodical - periodical + wordList + wordList - -- Journal - journal + computationalLexicon + computationalLexicon - --- Contribution to Journal - contribution to journal + ontology + ontology - ---- Journal Article - journal article + wordnet + wordnet - ----- Data Paper - data paper + thesaurus + thesaurus - ----- Review Article - review article + framenet + framenet - ----- Research Article - research article + terminologicalResource + terminologicalResource - ----- Corrigendum - corrigendum + machineReadableDictionary + machineReadableDictionary - ----- Software Paper - software paper + lexicon + lexicon - ---- Editorial - editorial + other + other + + - ---- Letter to the Editor - letter to the editor + N/A + - -- Newspaper - newspaper + grammar + grammar - --- Newspaper Article - newspaper article + machine learning model + mlmodel - -- Magazine - magazine + n-gram model + ngrammodel - - Patent - patent + other + other + + + + - - Preprint - preprint + N/A + + - - Report - report + terms + terms - -- Report Part - report part + entries + entries - -- Internal Report - internal report + turns + turns - -- Memorandum - memorandum + utterances + utterances - -- Other Type of Report - other type of report + articles + articles - -- Policy Report - policy report + files + files - -- Project Deliverable - project deliverable + items + items - --- Data Management Plan - data management plan + seconds + seconds - -- Report to Funding Agency - report to funding agency + elements + elements - -- Research Report - research report + units + units - -- Technical Report - technical report + minutes + minutes - - Research Proposal - research proposal + hours + hours - - Review - review + texts + texts - -- Book Review - book review + sentences + sentences - - Technical Documentation - technical documentation + pages + pages - - Working Paper - working paper + bytes + bytes - - Thesis - thesis + tokens + tokens - -- Bachelor Thesis - bachelor thesis + words + words - -- Doctoral Thesis - doctoral thesis + keywords + keywords - -- Master Thesis - master thesis + idiomaticExpressions + idiomaticExpressions - - Musical Notation - musical notation + neologisms + neologisms - - Blog Post - blog post + multiWordUnits + multiWordUnits - - Manuscript - website + expressions + expressions - Learning Object - learning object + synsets + synsets - Clinical Trial - clinical trial + classes + classes - Clinical Study - clinical study + concepts + concepts - - - - Author’s Original - http://purl.org/coar/version/c_b1a7d7d4d402bcce + lexicalTypes + lexicalTypes - Submitted Manuscript Under Review - http://purl.org/coar/version/c_71e4c1898caa6e32 + phoneticUnits + phoneticUnits - Accepted Manuscript - http://purl.org/coar/version/c_ab4af688f83e57aa + syntacticUnits + syntacticUnits - Proof - http://purl.org/coar/version/c_fa2ee174bc00049f + semanticUnits + semanticUnits - Version of Record - http://purl.org/coar/version/c_970fb48d4fbd8a85 + predicates + predicates - Corrected Version of Record - http://purl.org/coar/version/c_e19f295774971610 + phonemes + phonemes - Enhanced Version of Record - http://purl.org/coar/version/c_dc82b40f9837b551 + diphones + diphones - Not Applicable (or Unknown) - http://purl.org/coar/version/c_be7fb7dd8ff6fe43 + T-HPairs + T-HPairs - - - - open access - http://purl.org/coar/access_right/c_abf2 + syllables + syllables - embargoed access - http://purl.org/coar/access_right/c_f1cf + frames + frames - restricted access - http://purl.org/coar/access_right/c_16ec + images + images - metadata only access - http://purl.org/coar/access_right/c_14cb + kb + kb - - - - Scopus Author ID - scopus-author-id + mb + mb - Ciencia ID - ciencia-id + gb + gb - Google Scholar ID - gsid + rb + rb - Open Researcher and Contributor ID (ORCID) - orcid + shots + shots - Web of Science ResearcherID - rid + unigrams + unigrams - ISNI - International Standard Name Identifier - isni + bigrams + bigrams - Other - + trigrams + trigrams - - - - ISNI - International Standard Name Identifier - isni + 4-grams + 4-grams - Ringgold identifier - rin + 5-grams + 5-grams - Research Organization Registry - ror + n-grams + n-grams - Other - + rules + rules + + + other + other - + - N/A - + Ano + true - Is a Funding Organization - FundingOrganization + Ne + false - - Hidden + Ano hidden + + + teachingMaterials + teachingMaterials + + + - - - - + + + + - - - - - + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/dspace/solr/search/conf/schema.xml b/dspace/solr/search/conf/schema.xml index a80cfdedfd34..3a6125c1f9ac 100644 --- a/dspace/solr/search/conf/schema.xml +++ b/dspace/solr/search/conf/schema.xml @@ -126,6 +126,7 @@ + @@ -140,6 +141,7 @@ + - + @@ -194,11 +197,17 @@ - + + + + + + + - + - + @@ -361,5 +370,21 @@ + + + + + + + + + + + + + + + + diff --git a/dspace/solr/statistics/conf/solrconfig.xml b/dspace/solr/statistics/conf/solrconfig.xml index 2b1cff45373d..c3f023ff2eee 100644 --- a/dspace/solr/statistics/conf/solrconfig.xml +++ b/dspace/solr/statistics/conf/solrconfig.xml @@ -32,14 +32,16 @@ - + 32 1000 ${solr.lock.type:native} - + false @@ -48,7 +50,7 @@ 10000 - ${solr.autoCommit.maxTime:900000} + ${solr.autoCommit.maxTime:10000} true @@ -62,14 +64,16 @@ ${solr.max.booleanClauses:1024} + unordered sets of *all* documents that match a + query. Caches results of 'fq' search param. --> - + 1000 - + - + uuid @@ -126,7 +132,8 @@ - + uid