From c85463bc1850e0247ca3c2e3e33c5ee54103f2b1 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Mon, 14 Oct 2024 22:34:41 +0100 Subject: [PATCH 01/41] Frooodle/license (#1994) --- .gitignore | 1 - build.gradle | 13 +- cucumber/exampleFiles/example.html | 11 + cucumber/exampleFiles/example.md | 16 + cucumber/exampleFiles/example_html.zip | Bin 0 -> 284 bytes cucumber/features/external.feature | 30 +- .../docker-compose-latest-fat-security.yml | 4 +- .../software/SPDF/EE/EEAppConfig.java | 7 +- .../SPDF/EE/KeygenLicenseVerifier.java | 204 ++++++++++ .../software/SPDF/EE/LicenseKeyChecker.java | 59 +++ .../software/SPDF/LibreOfficeListener.java | 5 +- .../software/SPDF/config/AppConfig.java | 23 ++ .../SPDF/config/AppUpdateService.java | 1 + .../software/SPDF/config/InitialSetup.java | 42 ++ .../{Beans.java => LocaleConfiguration.java} | 2 +- .../software/SPDF/config/MetricsFilter.java | 6 +- .../software/SPDF/config/PostHogConfig.java | 34 ++ .../FingerprintBasedSessionFilter.java | 68 ++++ .../FingerprintBasedSessionManager.java | 134 +++++++ .../fingerprint/FingerprintGenerator.java | 77 ++++ .../DatabaseBackupInterface.java | 2 +- .../{ => interfaces}/ShowAdminInterface.java | 2 +- .../config/security/AppUpdateAuthService.java | 2 +- .../config/security/FirstLoginFilter.java | 21 + .../config/security/InitialSecuritySetup.java | 45 +-- .../security/SecurityConfiguration.java | 277 +++++-------- .../security/UserAuthenticationFilter.java | 33 +- .../SPDF/config/security/UserService.java | 6 +- .../database/DatabaseBackupHelper.java | 2 +- .../saml/ConvertResponseToAuthentication.java | 68 ++++ ...ustomSAMLAuthenticationFailureHandler.java | 51 +++ ...ustomSAMLAuthenticationSuccessHandler.java | 108 +++++ .../saml/SAMLLogoutSuccessHandler.java | 38 ++ .../saml/Saml2AuthorityAttributeLookup.java | 7 + .../Saml2AuthorityAttributeLookupImpl.java | 17 + .../security/saml/SamlAssertionUtils.java | 63 +++ .../SPDF/config/security/saml/SamlConfig.java | 42 ++ .../saml/ScimSaml2AuthenticatedPrincipal.java | 89 ++++ .../security/saml/SimpleScimMappings.java | 10 + .../session/CustomHttpSessionListener.java | 13 +- .../session/SessionPersistentRegistry.java | 8 + .../SPDF/controller/api/CropController.java | 7 +- .../controller/api/SettingsController.java | 37 ++ .../controller/api/SplitPDFController.java | 2 - .../api/SplitPdfByChaptersController.java | 2 +- .../SPDF/controller/api/UserController.java | 8 +- .../ConvertBookToPDFController.java | 6 +- .../api/converters/ConvertHtmlToPDF.java | 28 +- .../api/converters/ConvertMarkdownToPdf.java | 28 +- .../ConvertPDFToBookController.java | 4 +- .../api/converters/ConvertPDFToPDFA.java | 2 +- .../api/misc/ExtractImagesController.java | 2 - .../api/misc/PrintFileController.java | 4 +- .../api/security/RedactController.java | 2 - .../controller/web/GeneralWebController.java | 7 + .../SPDF/model/ApplicationProperties.java | 39 ++ .../software/SPDF/pdf/TextFinder.java | 4 +- .../SPDF/service/CustomPDDocumentFactory.java | 31 +- .../service/MetricsAggregatorService.java | 56 +++ .../PdfMetadataService.java | 59 ++- .../software/SPDF/service/PostHogService.java | 379 ++++++++++++++++++ .../SPDF/utils/CustomHtmlSanitizer.java | 21 + .../software/SPDF/utils/FileToPdf.java | 104 ++++- .../software/SPDF/utils/GeneralUtils.java | 87 ++++ .../software/SPDF/utils/PDFToFile.java | 1 - .../software/SPDF/utils/RequestUriUtils.java | 1 + .../misc/ReplaceAndInvertColorStrategy.java | 3 +- src/main/resources/application.properties | 6 +- src/main/resources/messages_ar_AR.properties | 52 ++- src/main/resources/messages_bg_BG.properties | 50 ++- src/main/resources/messages_ca_CA.properties | 52 ++- src/main/resources/messages_cs_CZ.properties | 52 ++- src/main/resources/messages_da_DK.properties | 50 ++- src/main/resources/messages_de_DE.properties | 52 ++- src/main/resources/messages_el_GR.properties | 50 ++- src/main/resources/messages_en_GB.properties | 58 ++- src/main/resources/messages_en_US.properties | 50 ++- src/main/resources/messages_es_ES.properties | 52 ++- src/main/resources/messages_eu_ES.properties | 50 ++- src/main/resources/messages_fr_FR.properties | 52 ++- src/main/resources/messages_ga_IE.properties | 50 ++- src/main/resources/messages_hi_IN.properties | 50 ++- src/main/resources/messages_hr_HR.properties | 52 ++- src/main/resources/messages_hu_HU.properties | 50 ++- src/main/resources/messages_id_ID.properties | 52 ++- src/main/resources/messages_it_IT.properties | 52 ++- src/main/resources/messages_ja_JP.properties | 52 ++- src/main/resources/messages_ko_KR.properties | 50 ++- src/main/resources/messages_nl_NL.properties | 52 ++- src/main/resources/messages_no_NB.properties | 50 ++- src/main/resources/messages_pl_PL.properties | 52 ++- src/main/resources/messages_pt_BR.properties | 52 ++- src/main/resources/messages_pt_PT.properties | 50 ++- src/main/resources/messages_ro_RO.properties | 50 ++- src/main/resources/messages_ru_RU.properties | 50 ++- src/main/resources/messages_sk_SK.properties | 52 ++- .../resources/messages_sr_LATN_RS.properties | 50 ++- src/main/resources/messages_sv_SE.properties | 50 ++- src/main/resources/messages_th_TH.properties | 50 ++- src/main/resources/messages_tr_TR.properties | 52 ++- src/main/resources/messages_uk_UA.properties | 50 ++- src/main/resources/messages_vi_VN.properties | 50 ++- src/main/resources/messages_zh_CN.properties | 50 ++- src/main/resources/messages_zh_TW.properties | 50 ++- src/main/resources/settings.yml.template | 5 +- .../resources/static/css/bootstrap.min.css | 2 +- src/main/resources/static/css/multi-tool.css | 2 +- src/main/resources/static/css/navbar.css | 31 ++ src/main/resources/static/css/rotate-pdf.css | 13 + src/main/resources/static/js/downloader.js | 23 +- src/main/resources/static/js/favourites.js | 68 +++- src/main/resources/static/js/homecard.js | 54 ++- .../static/js/multitool/PdfActionsManager.js | 1 + .../static/js/multitool/PdfContainer.js | 4 + .../static/js/multitool/horizontalScroll.js | 25 +- src/main/resources/templates/account.html | 6 +- src/main/resources/templates/addUsers.html | 2 + .../resources/templates/fragments/card.html | 2 +- .../resources/templates/fragments/common.html | 44 ++ .../resources/templates/fragments/footer.html | 2 +- .../resources/templates/fragments/navbar.html | 9 + src/main/resources/templates/home.html | 162 ++++++-- .../templates/split-pdf-by-chapters.html | 64 +++ test.sh | 48 +-- 124 files changed, 4399 insertions(+), 577 deletions(-) create mode 100644 cucumber/exampleFiles/example.html create mode 100644 cucumber/exampleFiles/example.md create mode 100644 cucumber/exampleFiles/example_html.zip create mode 100644 src/main/java/stirling/software/SPDF/EE/KeygenLicenseVerifier.java create mode 100644 src/main/java/stirling/software/SPDF/EE/LicenseKeyChecker.java create mode 100644 src/main/java/stirling/software/SPDF/config/InitialSetup.java rename src/main/java/stirling/software/SPDF/config/{Beans.java => LocaleConfiguration.java} (97%) create mode 100644 src/main/java/stirling/software/SPDF/config/PostHogConfig.java create mode 100644 src/main/java/stirling/software/SPDF/config/fingerprint/FingerprintBasedSessionFilter.java create mode 100644 src/main/java/stirling/software/SPDF/config/fingerprint/FingerprintBasedSessionManager.java create mode 100644 src/main/java/stirling/software/SPDF/config/fingerprint/FingerprintGenerator.java rename src/main/java/stirling/software/SPDF/config/{ => interfaces}/DatabaseBackupInterface.java (85%) rename src/main/java/stirling/software/SPDF/config/{ => interfaces}/ShowAdminInterface.java (69%) create mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/ConvertResponseToAuthentication.java create mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/CustomSAMLAuthenticationFailureHandler.java create mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/CustomSAMLAuthenticationSuccessHandler.java create mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/SAMLLogoutSuccessHandler.java create mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/Saml2AuthorityAttributeLookup.java create mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/Saml2AuthorityAttributeLookupImpl.java create mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/SamlAssertionUtils.java create mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/SamlConfig.java create mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/ScimSaml2AuthenticatedPrincipal.java create mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/SimpleScimMappings.java create mode 100644 src/main/java/stirling/software/SPDF/controller/api/SettingsController.java create mode 100644 src/main/java/stirling/software/SPDF/service/MetricsAggregatorService.java rename src/main/java/stirling/software/SPDF/{config => service}/PdfMetadataService.java (62%) create mode 100644 src/main/java/stirling/software/SPDF/service/PostHogService.java create mode 100644 src/main/java/stirling/software/SPDF/utils/CustomHtmlSanitizer.java create mode 100644 src/main/resources/templates/split-pdf-by-chapters.html diff --git a/.gitignore b/.gitignore index ba9d755d98d..c5a8f2e7693 100644 --- a/.gitignore +++ b/.gitignore @@ -110,7 +110,6 @@ watchedFolders/ *.war *.nar *.ear -*.zip *.tar.gz *.rar *.db diff --git a/build.gradle b/build.gradle index 860a574a133..f468388283a 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ ext { } group = "stirling.software" -version = "0.29.0" +version = "0.30.0" java { // 17 is lowest but we support and recommend 21 @@ -32,6 +32,12 @@ java { repositories { mavenCentral() maven { url "https://jitpack.io" } + maven { + url "https://build.shibboleth.net/nexus/content/repositories/releases/" + } + maven { + url "https://build.shibboleth.net/maven/releases/" + } } licenseReport { @@ -127,6 +133,9 @@ dependencies { implementation "org.springframework.boot:spring-boot-starter-jetty:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion" + implementation 'com.posthog.java:posthog:1.1.1' + implementation 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1' + if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") { implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion" @@ -134,6 +143,8 @@ dependencies { implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion" + implementation 'org.springframework.security:spring-security-saml2-service-provider:6.3.3' + implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5' //2.2.x requires rebuild of DB file.. need migration path runtimeOnly "com.h2database:h2:2.1.214" // implementation "com.h2database:h2:2.2.224" diff --git a/cucumber/exampleFiles/example.html b/cucumber/exampleFiles/example.html new file mode 100644 index 00000000000..82e96100735 --- /dev/null +++ b/cucumber/exampleFiles/example.html @@ -0,0 +1,11 @@ + + + + +

My First Heading

+ +

My first paragraph.

+ + + + diff --git a/cucumber/exampleFiles/example.md b/cucumber/exampleFiles/example.md new file mode 100644 index 00000000000..10bb117bad8 --- /dev/null +++ b/cucumber/exampleFiles/example.md @@ -0,0 +1,16 @@ +header +============ + +Header2 +------------ +text + +text2 + +## **PDF Features** + +### **Page Operations** + +- View and modify PDFs - View multi page PDFs with custom viewing sorting and searching. Plus on page edit features like annotate, draw and adding text and images. (Using PDF.js with Joxit and Liberation.Liberation fonts) +- Full interactive GUI for merging/splitting/rotating/moving PDFs and their pages. +- Merge multiple PDFs together into a single resultant file. \ No newline at end of file diff --git a/cucumber/exampleFiles/example_html.zip b/cucumber/exampleFiles/example_html.zip new file mode 100644 index 0000000000000000000000000000000000000000..23bd1950c444d74aff9bee2916f8d44526dbf80b GIT binary patch literal 284 zcmWIWW@Zs#-~horu8|B-Pz0oT7!(*%D-v@Ha#Hm&N^)~TLwFh33-&Rl*8y=d5SLbP zGcdA%bbyJ?fh8Zy`8s(DLQiO%JiGDn(@l*RJ%4IWb&yg?%6J~V^1-yokeEdeCeE5M zY3B62#rz^H-eL_;E*&Uh?R4#Y>DtLES!MQ%RUoo response = client.send(request, HttpResponse.BodyHandlers.ofString()); + log.info(" validateLicenseResponse body: " + response.body()); + JsonNode jsonResponse = objectMapper.readTree(response.body()); + if (response.statusCode() == 200) { + + JsonNode metaNode = jsonResponse.path("meta"); + boolean isValid = metaNode.path("valid").asBoolean(); + + String detail = metaNode.path("detail").asText(); + String code = metaNode.path("code").asText(); + + log.debug("License validity: " + isValid); + log.debug("Validation detail: " + detail); + log.debug("Validation code: " + code); + + int users = + jsonResponse + .path("data") + .path("attributes") + .path("metadata") + .path("users") + .asInt(0); + applicationProperties.getEnterpriseEdition().setMaxUsers(users); + log.info(applicationProperties.toString()); + + } else { + log.error("Error validating license. Status code: " + response.statusCode()); + } + return jsonResponse; + } + + private boolean activateMachine(String licenseKey, String licenseId, String machineFingerprint) + throws Exception { + HttpClient client = HttpClient.newHttpClient(); + + String hostname; + try { + hostname = java.net.InetAddress.getLocalHost().getHostName(); + } catch (Exception e) { + hostname = "Unknown"; + } + + JSONObject body = + new JSONObject() + .put( + "data", + new JSONObject() + .put("type", "machines") + .put( + "attributes", + new JSONObject() + .put("fingerprint", machineFingerprint) + .put( + "platform", + System.getProperty( + "os.name")) // Added + // platform + // parameter + .put( + "name", + hostname)) // Added name parameter + .put( + "relationships", + new JSONObject() + .put( + "license", + new JSONObject() + .put( + "data", + new JSONObject() + .put( + "type", + "licenses") + .put( + "id", + licenseId))))); + + HttpRequest request = + HttpRequest.newBuilder() + .uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/machines")) + .header("Content-Type", "application/vnd.api+json") + .header("Accept", "application/vnd.api+json") + .header( + "Authorization", + "License " + licenseKey) // Keep the license key authentication + .POST( + HttpRequest.BodyPublishers.ofString( + body.toString())) // Send the JSON body + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + log.debug("activateMachine Response body: " + response.body()); + if (response.statusCode() == 201) { + log.info("Machine activated successfully"); + return true; + } else { + log.error( + "Error activating machine. Status code: {}, error: {}", + response.statusCode(), + response.body()); + + return false; + } + } + + private String generateMachineFingerprint() { + return GeneralUtils.generateMachineFingerprint(); + } +} diff --git a/src/main/java/stirling/software/SPDF/EE/LicenseKeyChecker.java b/src/main/java/stirling/software/SPDF/EE/LicenseKeyChecker.java new file mode 100644 index 00000000000..3da7da05d9d --- /dev/null +++ b/src/main/java/stirling/software/SPDF/EE/LicenseKeyChecker.java @@ -0,0 +1,59 @@ +package stirling.software.SPDF.EE; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; +import stirling.software.SPDF.model.ApplicationProperties; +import stirling.software.SPDF.utils.GeneralUtils; + +@Component +@Slf4j +public class LicenseKeyChecker { + + private final KeygenLicenseVerifier licenseService; + + private final ApplicationProperties applicationProperties; + + private boolean enterpriseEnbaledResult = false; + + @Autowired + public LicenseKeyChecker( + KeygenLicenseVerifier licenseService, ApplicationProperties applicationProperties) { + this.licenseService = licenseService; + this.applicationProperties = applicationProperties; + } + + @Scheduled(fixedRate = 604800000, initialDelay = 1000) // 7 days in milliseconds + public void checkLicensePeriodically() { + checkLicense(); + } + + private void checkLicense() { + if (!applicationProperties.getEnterpriseEdition().isEnabled()) { + enterpriseEnbaledResult = false; + } else { + enterpriseEnbaledResult = + licenseService.verifyLicense( + applicationProperties.getEnterpriseEdition().getKey()); + if (enterpriseEnbaledResult) { + log.info("License key is valid."); + } else { + log.info("License key is invalid."); + } + } + } + + public void updateLicenseKey(String newKey) throws IOException { + applicationProperties.getEnterpriseEdition().setKey(newKey); + GeneralUtils.saveKeyToConfig("EnterpriseEdition.key", newKey, false); + checkLicense(); + } + + public boolean getEnterpriseEnabledResult() { + return enterpriseEnbaledResult; + } +} diff --git a/src/main/java/stirling/software/SPDF/LibreOfficeListener.java b/src/main/java/stirling/software/SPDF/LibreOfficeListener.java index 06e195126ca..549a009268a 100644 --- a/src/main/java/stirling/software/SPDF/LibreOfficeListener.java +++ b/src/main/java/stirling/software/SPDF/LibreOfficeListener.java @@ -11,6 +11,9 @@ import io.github.pixee.security.SystemCommand; +import lombok.extern.slf4j.Slf4j; + +@Slf4j public class LibreOfficeListener { private static final Logger logger = LoggerFactory.getLogger(LibreOfficeListener.class); @@ -31,7 +34,7 @@ public static LibreOfficeListener getInstance() { private LibreOfficeListener() {} private boolean isListenerRunning() { - System.out.println("waiting for listener to start"); + log.info("waiting for listener to start"); try (Socket socket = new Socket()) { socket.connect( new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second diff --git a/src/main/java/stirling/software/SPDF/config/AppConfig.java b/src/main/java/stirling/software/SPDF/config/AppConfig.java index 6fc09917614..3391a50f009 100644 --- a/src/main/java/stirling/software/SPDF/config/AppConfig.java +++ b/src/main/java/stirling/software/SPDF/config/AppConfig.java @@ -160,4 +160,27 @@ public String impressum() { public String accessibilityStatement() { return applicationProperties.getLegal().getAccessibilityStatement(); } + + @Bean(name = "analyticsPrompt") + public boolean analyticsPrompt() { + return applicationProperties.getSystem().getEnableAnalytics() == null + || "undefined".equals(applicationProperties.getSystem().getEnableAnalytics()); + } + + @Bean(name = "analyticsEnabled") + public boolean analyticsEnabled() { + if (applicationProperties.getEnterpriseEdition().isEnabled()) return true; + return applicationProperties.getSystem().getEnableAnalytics() != null + && Boolean.parseBoolean(applicationProperties.getSystem().getEnableAnalytics()); + } + + @Bean(name = "StirlingPDFLabel") + public String stirlingPDFLabel() { + return "Stirling-PDF" + " v" + appVersion(); + } + + @Bean(name = "UUID") + public String uuid() { + return applicationProperties.getAutomaticallyGenerated().getUUID(); + } } diff --git a/src/main/java/stirling/software/SPDF/config/AppUpdateService.java b/src/main/java/stirling/software/SPDF/config/AppUpdateService.java index 7fc87629083..3eb204887cf 100644 --- a/src/main/java/stirling/software/SPDF/config/AppUpdateService.java +++ b/src/main/java/stirling/software/SPDF/config/AppUpdateService.java @@ -5,6 +5,7 @@ import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Service; +import stirling.software.SPDF.config.interfaces.ShowAdminInterface; import stirling.software.SPDF.model.ApplicationProperties; @Service diff --git a/src/main/java/stirling/software/SPDF/config/InitialSetup.java b/src/main/java/stirling/software/SPDF/config/InitialSetup.java new file mode 100644 index 00000000000..043b08c994d --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/InitialSetup.java @@ -0,0 +1,42 @@ +package stirling.software.SPDF.config; + +import java.io.IOException; +import java.util.UUID; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import stirling.software.SPDF.model.ApplicationProperties; +import stirling.software.SPDF.utils.GeneralUtils; + +@Component +@Slf4j +@Order(Ordered.HIGHEST_PRECEDENCE + 1) +public class InitialSetup { + + @Autowired private ApplicationProperties applicationProperties; + + @PostConstruct + public void initUUIDKey() throws IOException { + String uuid = applicationProperties.getAutomaticallyGenerated().getUUID(); + if (!GeneralUtils.isValidUUID(uuid)) { + uuid = UUID.randomUUID().toString(); // Generating a random UUID as the secret key + GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.UUID", uuid); + applicationProperties.getAutomaticallyGenerated().setUUID(uuid); + } + } + + @PostConstruct + public void initSecretKey() throws IOException { + String secretKey = applicationProperties.getAutomaticallyGenerated().getKey(); + if (!GeneralUtils.isValidUUID(secretKey)) { + secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key + GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.key", secretKey); + applicationProperties.getAutomaticallyGenerated().setKey(secretKey); + } + } +} diff --git a/src/main/java/stirling/software/SPDF/config/Beans.java b/src/main/java/stirling/software/SPDF/config/LocaleConfiguration.java similarity index 97% rename from src/main/java/stirling/software/SPDF/config/Beans.java rename to src/main/java/stirling/software/SPDF/config/LocaleConfiguration.java index 03084b2419f..a15d6c165b9 100644 --- a/src/main/java/stirling/software/SPDF/config/Beans.java +++ b/src/main/java/stirling/software/SPDF/config/LocaleConfiguration.java @@ -14,7 +14,7 @@ import stirling.software.SPDF.model.ApplicationProperties; @Configuration -public class Beans implements WebMvcConfigurer { +public class LocaleConfiguration implements WebMvcConfigurer { @Autowired ApplicationProperties applicationProperties; diff --git a/src/main/java/stirling/software/SPDF/config/MetricsFilter.java b/src/main/java/stirling/software/SPDF/config/MetricsFilter.java index c0231ca7f35..fba1ee9ce04 100644 --- a/src/main/java/stirling/software/SPDF/config/MetricsFilter.java +++ b/src/main/java/stirling/software/SPDF/config/MetricsFilter.java @@ -13,6 +13,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import stirling.software.SPDF.utils.RequestUriUtils; @Component @@ -32,10 +33,11 @@ protected void doFilterInternal( String uri = request.getRequestURI(); if (RequestUriUtils.isTrackableResource(request.getContextPath(), uri)) { - + HttpSession session = request.getSession(false); + String sessionId = (session != null) ? session.getId() : "no-session"; Counter counter = Counter.builder("http.requests") - .tag("session", request.getSession().getId()) + .tag("session", sessionId) .tag("method", request.getMethod()) .tag("uri", uri) .register(meterRegistry); diff --git a/src/main/java/stirling/software/SPDF/config/PostHogConfig.java b/src/main/java/stirling/software/SPDF/config/PostHogConfig.java new file mode 100644 index 00000000000..42d5b10146a --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/PostHogConfig.java @@ -0,0 +1,34 @@ +package stirling.software.SPDF.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.posthog.java.PostHog; + +import jakarta.annotation.PreDestroy; + +@Configuration +public class PostHogConfig { + + @Value("${posthog.api.key}") + private String posthogApiKey; + + @Value("${posthog.host}") + private String posthogHost; + + private PostHog postHogClient; + + @Bean + public PostHog postHogClient() { + postHogClient = new PostHog.Builder(posthogApiKey).host(posthogHost).build(); + return postHogClient; + } + + @PreDestroy + public void shutdownPostHog() { + if (postHogClient != null) { + postHogClient.shutdown(); + } + } +} diff --git a/src/main/java/stirling/software/SPDF/config/fingerprint/FingerprintBasedSessionFilter.java b/src/main/java/stirling/software/SPDF/config/fingerprint/FingerprintBasedSessionFilter.java new file mode 100644 index 00000000000..3815b95bd27 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/fingerprint/FingerprintBasedSessionFilter.java @@ -0,0 +1,68 @@ +// package stirling.software.SPDF.config.fingerprint; +// +// import java.io.IOException; +// +// import org.springframework.beans.factory.annotation.Autowired; +// import org.springframework.stereotype.Component; +// import org.springframework.web.filter.OncePerRequestFilter; +// +// import jakarta.servlet.FilterChain; +// import jakarta.servlet.ServletException; +// import jakarta.servlet.http.HttpServletRequest; +// import jakarta.servlet.http.HttpServletResponse; +// import jakarta.servlet.http.HttpSession; +// import lombok.extern.slf4j.Slf4j; +// import stirling.software.SPDF.utils.RequestUriUtils; +// +//// @Component +// @Slf4j +// public class FingerprintBasedSessionFilter extends OncePerRequestFilter { +// private final FingerprintGenerator fingerprintGenerator; +// private final FingerprintBasedSessionManager sessionManager; +// +// @Autowired +// public FingerprintBasedSessionFilter( +// FingerprintGenerator fingerprintGenerator, +// FingerprintBasedSessionManager sessionManager) { +// this.fingerprintGenerator = fingerprintGenerator; +// this.sessionManager = sessionManager; +// } +// +// @Override +// protected void doFilterInternal( +// HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) +// throws ServletException, IOException { +// +// if (RequestUriUtils.isStaticResource(request.getContextPath(), request.getRequestURI())) { +// filterChain.doFilter(request, response); +// return; +// } +// +// String fingerprint = fingerprintGenerator.generateFingerprint(request); +// log.debug("Generated fingerprint for request: {}", fingerprint); +// +// HttpSession session = request.getSession(); +// boolean isNewSession = session.isNew(); +// String sessionId = session.getId(); +// +// if (isNewSession) { +// log.info("New session created: {}", sessionId); +// } +// +// if (!sessionManager.isFingerPrintAllowed(fingerprint)) { +// log.info("Blocked fingerprint detected, redirecting: {}", fingerprint); +// response.sendRedirect(request.getContextPath() + "/too-many-requests"); +// return; +// } +// +// session.setAttribute("userFingerprint", fingerprint); +// session.setAttribute( +// FingerprintBasedSessionManager.STARTUP_TIMESTAMP, +// FingerprintBasedSessionManager.APP_STARTUP_TIME); +// +// sessionManager.registerFingerprint(fingerprint, sessionId); +// +// log.debug("Proceeding with request: {}", request.getRequestURI()); +// filterChain.doFilter(request, response); +// } +// } diff --git a/src/main/java/stirling/software/SPDF/config/fingerprint/FingerprintBasedSessionManager.java b/src/main/java/stirling/software/SPDF/config/fingerprint/FingerprintBasedSessionManager.java new file mode 100644 index 00000000000..3854f62b130 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/fingerprint/FingerprintBasedSessionManager.java @@ -0,0 +1,134 @@ +// package stirling.software.SPDF.config.fingerprint; +// +// import java.util.Iterator; +// import java.util.Map; +// import java.util.concurrent.ConcurrentHashMap; +// import java.util.concurrent.TimeUnit; +// +// import org.springframework.scheduling.annotation.Scheduled; +// import org.springframework.stereotype.Component; +// +// import jakarta.servlet.http.HttpSession; +// import jakarta.servlet.http.HttpSessionAttributeListener; +// import jakarta.servlet.http.HttpSessionEvent; +// import jakarta.servlet.http.HttpSessionListener; +// import lombok.AllArgsConstructor; +// import lombok.Data; +// import lombok.extern.slf4j.Slf4j; +// +// @Slf4j +// @Component +// public class FingerprintBasedSessionManager +// implements HttpSessionListener, HttpSessionAttributeListener { +// private static final ConcurrentHashMap activeFingerprints = +// new ConcurrentHashMap<>(); +// +// // To be reduced in later version to 8~ +// private static final int MAX_ACTIVE_FINGERPRINTS = 30; +// +// static final String STARTUP_TIMESTAMP = "appStartupTimestamp"; +// static final long APP_STARTUP_TIME = System.currentTimeMillis(); +// private static final long FINGERPRINT_EXPIRATION = TimeUnit.MINUTES.toMillis(30); +// +// @Override +// public void sessionCreated(HttpSessionEvent se) { +// HttpSession session = se.getSession(); +// String sessionId = session.getId(); +// String fingerprint = (String) session.getAttribute("userFingerprint"); +// +// if (fingerprint == null) { +// log.warn("Session created without fingerprint: {}", sessionId); +// return; +// } +// +// synchronized (activeFingerprints) { +// if (activeFingerprints.size() >= MAX_ACTIVE_FINGERPRINTS +// && !activeFingerprints.containsKey(fingerprint)) { +// log.info("Max fingerprints reached. Marking session as blocked: {}", sessionId); +// session.setAttribute("blocked", true); +// } else { +// activeFingerprints.put( +// fingerprint, new FingerprintInfo(sessionId, System.currentTimeMillis())); +// log.info( +// "New fingerprint registered: {}. Total active fingerprints: {}", +// fingerprint, +// activeFingerprints.size()); +// } +// session.setAttribute(STARTUP_TIMESTAMP, APP_STARTUP_TIME); +// } +// } +// +// @Override +// public void sessionDestroyed(HttpSessionEvent se) { +// HttpSession session = se.getSession(); +// String fingerprint = (String) session.getAttribute("userFingerprint"); +// +// if (fingerprint != null) { +// synchronized (activeFingerprints) { +// activeFingerprints.remove(fingerprint); +// log.info( +// "Fingerprint removed: {}. Total active fingerprints: {}", +// fingerprint, +// activeFingerprints.size()); +// } +// } +// } +// +// public boolean isFingerPrintAllowed(String fingerprint) { +// synchronized (activeFingerprints) { +// return activeFingerprints.size() < MAX_ACTIVE_FINGERPRINTS +// || activeFingerprints.containsKey(fingerprint); +// } +// } +// +// public void registerFingerprint(String fingerprint, String sessionId) { +// synchronized (activeFingerprints) { +// activeFingerprints.put( +// fingerprint, new FingerprintInfo(sessionId, System.currentTimeMillis())); +// } +// } +// +// public void unregisterFingerprint(String fingerprint) { +// synchronized (activeFingerprints) { +// activeFingerprints.remove(fingerprint); +// } +// } +// +// @Scheduled(fixedRate = 1800000) // Run every 30 mins +// public void cleanupStaleFingerprints() { +// log.info("Starting cleanup of stale fingerprints"); +// long now = System.currentTimeMillis(); +// int removedCount = 0; +// +// synchronized (activeFingerprints) { +// Iterator> iterator = +// activeFingerprints.entrySet().iterator(); +// while (iterator.hasNext()) { +// Map.Entry entry = iterator.next(); +// FingerprintInfo info = entry.getValue(); +// +// if (now - info.getLastAccessTime() > FINGERPRINT_EXPIRATION) { +// iterator.remove(); +// removedCount++; +// log.info("Removed stale fingerprint: {}", entry.getKey()); +// } +// } +// } +// +// log.info("Cleanup complete. Removed {} stale fingerprints", removedCount); +// } +// +// public void updateLastAccessTime(String fingerprint) { +// FingerprintInfo info = activeFingerprints.get(fingerprint); +// if (info != null) { +// info.setLastAccessTime(System.currentTimeMillis()); +// } +// } +// +// @Data +// @AllArgsConstructor +// private static class FingerprintInfo { +// private String sessionId; +// private long lastAccessTime; +// } +// } diff --git a/src/main/java/stirling/software/SPDF/config/fingerprint/FingerprintGenerator.java b/src/main/java/stirling/software/SPDF/config/fingerprint/FingerprintGenerator.java new file mode 100644 index 00000000000..f1a65cf8c56 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/fingerprint/FingerprintGenerator.java @@ -0,0 +1,77 @@ +// package stirling.software.SPDF.config.fingerprint; +// +// import java.security.MessageDigest; +// import java.security.NoSuchAlgorithmException; +// +// import org.springframework.stereotype.Component; +// +// import jakarta.servlet.http.HttpServletRequest; +// +// @Component +// public class FingerprintGenerator { +// +// public String generateFingerprint(HttpServletRequest request) { +// if (request == null) { +// return ""; +// } +// StringBuilder fingerprintBuilder = new StringBuilder(); +// +// // Add IP address +// fingerprintBuilder.append(request.getRemoteAddr()); +// +// // Add X-Forwarded-For header if present (for clients behind proxies) +// String forwardedFor = request.getHeader("X-Forwarded-For"); +// if (forwardedFor != null) { +// fingerprintBuilder.append(forwardedFor); +// } +// +// // Add User-Agent +// String userAgent = request.getHeader("User-Agent"); +// if (userAgent != null) { +// fingerprintBuilder.append(userAgent); +// } +// +// // Add Accept-Language header +// String acceptLanguage = request.getHeader("Accept-Language"); +// if (acceptLanguage != null) { +// fingerprintBuilder.append(acceptLanguage); +// } +// +// // Add Accept header +// String accept = request.getHeader("Accept"); +// if (accept != null) { +// fingerprintBuilder.append(accept); +// } +// +// // Add Connection header +// String connection = request.getHeader("Connection"); +// if (connection != null) { +// fingerprintBuilder.append(connection); +// } +// +// // Add server port +// fingerprintBuilder.append(request.getServerPort()); +// +// // Add secure flag +// fingerprintBuilder.append(request.isSecure()); +// +// // Generate a hash of the fingerprint +// return generateHash(fingerprintBuilder.toString()); +// } +// +// private String generateHash(String input) { +// try { +// MessageDigest digest = MessageDigest.getInstance("SHA-256"); +// byte[] hash = digest.digest(input.getBytes()); +// StringBuilder hexString = new StringBuilder(); +// for (byte b : hash) { +// String hex = Integer.toHexString(0xff & b); +// if (hex.length() == 1) hexString.append('0'); +// hexString.append(hex); +// } +// return hexString.toString(); +// } catch (NoSuchAlgorithmException e) { +// throw new RuntimeException("Failed to generate fingerprint hash", e); +// } +// } +// } diff --git a/src/main/java/stirling/software/SPDF/config/DatabaseBackupInterface.java b/src/main/java/stirling/software/SPDF/config/interfaces/DatabaseBackupInterface.java similarity index 85% rename from src/main/java/stirling/software/SPDF/config/DatabaseBackupInterface.java rename to src/main/java/stirling/software/SPDF/config/interfaces/DatabaseBackupInterface.java index 267981d1888..3ad11f2a6c1 100644 --- a/src/main/java/stirling/software/SPDF/config/DatabaseBackupInterface.java +++ b/src/main/java/stirling/software/SPDF/config/interfaces/DatabaseBackupInterface.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.config; +package stirling.software.SPDF.config.interfaces; import java.io.IOException; import java.util.List; diff --git a/src/main/java/stirling/software/SPDF/config/ShowAdminInterface.java b/src/main/java/stirling/software/SPDF/config/interfaces/ShowAdminInterface.java similarity index 69% rename from src/main/java/stirling/software/SPDF/config/ShowAdminInterface.java rename to src/main/java/stirling/software/SPDF/config/interfaces/ShowAdminInterface.java index e49376e27c2..1bbebf5a998 100644 --- a/src/main/java/stirling/software/SPDF/config/ShowAdminInterface.java +++ b/src/main/java/stirling/software/SPDF/config/interfaces/ShowAdminInterface.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.config; +package stirling.software.SPDF.config.interfaces; public interface ShowAdminInterface { default boolean getShowUpdateOnlyAdmins() { diff --git a/src/main/java/stirling/software/SPDF/config/security/AppUpdateAuthService.java b/src/main/java/stirling/software/SPDF/config/security/AppUpdateAuthService.java index 5a16aa308ab..57ce7b7d6c5 100644 --- a/src/main/java/stirling/software/SPDF/config/security/AppUpdateAuthService.java +++ b/src/main/java/stirling/software/SPDF/config/security/AppUpdateAuthService.java @@ -7,7 +7,7 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; -import stirling.software.SPDF.config.ShowAdminInterface; +import stirling.software.SPDF.config.interfaces.ShowAdminInterface; import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.User; import stirling.software.SPDF.repository.UserRepository; diff --git a/src/main/java/stirling/software/SPDF/config/security/FirstLoginFilter.java b/src/main/java/stirling/software/SPDF/config/security/FirstLoginFilter.java index 86afa0287af..babba64d280 100644 --- a/src/main/java/stirling/software/SPDF/config/security/FirstLoginFilter.java +++ b/src/main/java/stirling/software/SPDF/config/security/FirstLoginFilter.java @@ -1,6 +1,8 @@ package stirling.software.SPDF.config.security; import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; @@ -14,9 +16,12 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.model.User; import stirling.software.SPDF.utils.RequestUriUtils; +@Slf4j @Component public class FirstLoginFilter extends OncePerRequestFilter { @@ -50,6 +55,22 @@ protected void doFilterInternal( return; } } + + if (log.isDebugEnabled()) { + HttpSession session = request.getSession(true); + SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss"); + String creationTime = timeFormat.format(new Date(session.getCreationTime())); + + log.debug( + "Request Info - New: {}, creationTimeSession {}, ID: {}, IP: {}, User-Agent: {}, Referer: {}, Request URL: {}", + session.isNew(), + creationTime, + session.getId(), + request.getRemoteAddr(), + request.getHeader("User-Agent"), + request.getHeader("Referer"), + request.getRequestURL().toString()); + } filterChain.doFilter(request, response); } } diff --git a/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java b/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java index b7442c5b2f1..f43baf0a392 100644 --- a/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java +++ b/src/main/java/stirling/software/SPDF/config/security/InitialSecuritySetup.java @@ -1,19 +1,14 @@ package stirling.software.SPDF.config.security; import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.UUID; -import org.simpleyaml.configuration.file.YamlFile; -import org.simpleyaml.configuration.implementation.SimpleYamlImplementation; -import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; -import stirling.software.SPDF.config.DatabaseBackupInterface; +import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface; import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.Role; @@ -39,15 +34,6 @@ public void init() throws IllegalArgumentException, IOException { initializeInternalApiUser(); } - @PostConstruct - public void initSecretKey() throws IOException { - String secretKey = applicationProperties.getAutomaticallyGenerated().getKey(); - if (!isValidUUID(secretKey)) { - secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key - saveKeyToConfig(secretKey); - } - } - private void initializeAdminUser() throws IOException { String initialUsername = applicationProperties.getSecurity().getInitialLogin().getUsername(); @@ -89,33 +75,4 @@ private void initializeInternalApiUser() throws IllegalArgumentException, IOExce log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId()); } } - - private void saveKeyToConfig(String key) throws IOException { - Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml - - final YamlFile settingsYml = new YamlFile(path.toFile()); - DumperOptions yamlOptionssettingsYml = - ((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions(); - yamlOptionssettingsYml.setSplitLines(false); - - settingsYml.loadWithComments(); - - settingsYml - .path("AutomaticallyGenerated.key") - .set(key) - .comment("# Automatically Generated Settings (Do Not Edit Directly)"); - settingsYml.save(); - } - - private boolean isValidUUID(String uuid) { - if (uuid == null) { - return false; - } - try { - UUID.fromString(uuid); - return true; - } catch (IllegalArgumentException e) { - return false; - } - } } diff --git a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java index aa266d2f144..79e40e2bb7f 100644 --- a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java @@ -1,57 +1,55 @@ package stirling.software.SPDF.config.security; -import java.util.*; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.ClientRegistrations; -import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; -import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; +import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; +import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.savedrequest.NullRequestCache; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler; import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler; import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler; import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService; +import stirling.software.SPDF.config.security.saml.ConvertResponseToAuthentication; +import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationFailureHandler; +import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationSuccessHandler; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.model.ApplicationProperties; -import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; -import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client; -import stirling.software.SPDF.model.User; -import stirling.software.SPDF.model.provider.GithubProvider; -import stirling.software.SPDF.model.provider.GoogleProvider; -import stirling.software.SPDF.model.provider.KeycloakProvider; import stirling.software.SPDF.repository.JPATokenRepositoryImpl; @Configuration @EnableWebSecurity @EnableMethodSecurity +@Slf4j public class SecurityConfiguration { @Autowired private CustomUserDetailsService userDetailsService; - private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class); + @Autowired(required = false) + private GrantedAuthoritiesMapper userAuthoritiesMapper; + + @Autowired(required = false) + private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository; @Bean public PasswordEncoder passwordEncoder() { @@ -73,13 +71,18 @@ public PasswordEncoder passwordEncoder() { @Autowired private FirstLoginFilter firstLoginFilter; @Autowired private SessionPersistentRegistry sessionRegistry; + @Autowired private ConvertResponseToAuthentication convertResponseToAuthentication; + @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + http.authenticationManager(authenticationManager(http)); if (loginEnabledValue) { - - http.csrf(csrf -> csrf.disable()); + http.addFilterBefore( + userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + if (applicationProperties.getSecurity().getCsrfDisabled()) { + http.csrf(csrf -> csrf.disable()); + } http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class); http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class); http.sessionManagement( @@ -135,6 +138,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return trimmedUri.startsWith("/login") || trimmedUri.startsWith("/oauth") + || trimmedUri.startsWith("/saml2") || trimmedUri.endsWith(".svg") || trimmedUri.startsWith( "/register") @@ -184,191 +188,82 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { userService, loginAttemptService)) .userAuthoritiesMapper( - userAuthoritiesMapper()))) + userAuthoritiesMapper))) .logout( logout -> logout.logoutSuccessHandler( new CustomOAuth2LogoutSuccessHandler( applicationProperties))); } + + // Handle SAML + if (applicationProperties.getSecurity().getSaml() != null + && applicationProperties.getSecurity().getSaml().getEnabled() + && !applicationProperties + .getSecurity() + .getLoginMethod() + .equalsIgnoreCase("normal")) { + http.saml2Login( + saml2 -> { + saml2.loginPage("/saml2") + .relyingPartyRegistrationRepository( + relyingPartyRegistrationRepository) + .successHandler( + new CustomSAMLAuthenticationSuccessHandler( + loginAttemptService, + userService, + applicationProperties)) + .failureHandler( + new CustomSAMLAuthenticationFailureHandler()); + }) + .addFilterBefore( + userAuthenticationFilter, Saml2WebSsoAuthenticationFilter.class); + } } else { - http.csrf(csrf -> csrf.disable()) - .authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); + if (applicationProperties.getSecurity().getCsrfDisabled()) { + http.csrf(csrf -> csrf.disable()); + } + http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); } return http.build(); } - // Client Registration Repository for OAUTH2 OIDC Login @Bean @ConditionalOnProperty( - value = "security.oauth2.enabled", + name = "security.saml.enabled", havingValue = "true", matchIfMissing = false) - public ClientRegistrationRepository clientRegistrationRepository() { - List registrations = new ArrayList<>(); - - githubClientRegistration().ifPresent(registrations::add); - oidcClientRegistration().ifPresent(registrations::add); - googleClientRegistration().ifPresent(registrations::add); - keycloakClientRegistration().ifPresent(registrations::add); - - if (registrations.isEmpty()) { - logger.error("At least one OAuth2 provider must be configured"); - System.exit(1); - } - - return new InMemoryClientRegistrationRepository(registrations); + public AuthenticationProvider samlAuthenticationProvider() { + OpenSaml4AuthenticationProvider authenticationProvider = + new OpenSaml4AuthenticationProvider(); + authenticationProvider.setResponseAuthenticationConverter(convertResponseToAuthentication); + return authenticationProvider; } - private Optional googleClientRegistration() { - OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); - if (oauth == null || !oauth.getEnabled()) { - return Optional.empty(); - } - Client client = oauth.getClient(); - if (client == null) { - return Optional.empty(); - } - GoogleProvider google = client.getGoogle(); - return google != null && google.isSettingsValid() - ? Optional.of( - ClientRegistration.withRegistrationId(google.getName()) - .clientId(google.getClientId()) - .clientSecret(google.getClientSecret()) - .scope(google.getScopes()) - .authorizationUri(google.getAuthorizationuri()) - .tokenUri(google.getTokenuri()) - .userInfoUri(google.getUserinfouri()) - .userNameAttributeName(google.getUseAsUsername()) - .clientName(google.getClientName()) - .redirectUri("{baseUrl}/login/oauth2/code/" + google.getName()) - .authorizationGrantType( - org.springframework.security.oauth2.core - .AuthorizationGrantType.AUTHORIZATION_CODE) - .build()) - : Optional.empty(); - } - - private Optional keycloakClientRegistration() { - OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); - if (oauth == null || !oauth.getEnabled()) { - return Optional.empty(); - } - Client client = oauth.getClient(); - if (client == null) { - return Optional.empty(); - } - KeycloakProvider keycloak = client.getKeycloak(); - - return keycloak != null && keycloak.isSettingsValid() - ? Optional.of( - ClientRegistrations.fromIssuerLocation(keycloak.getIssuer()) - .registrationId(keycloak.getName()) - .clientId(keycloak.getClientId()) - .clientSecret(keycloak.getClientSecret()) - .scope(keycloak.getScopes()) - .userNameAttributeName(keycloak.getUseAsUsername()) - .clientName(keycloak.getClientName()) - .build()) - : Optional.empty(); - } - - private Optional githubClientRegistration() { - OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); - if (oauth == null || !oauth.getEnabled()) { - return Optional.empty(); - } - Client client = oauth.getClient(); - if (client == null) { - return Optional.empty(); - } - GithubProvider github = client.getGithub(); - return github != null && github.isSettingsValid() - ? Optional.of( - ClientRegistration.withRegistrationId(github.getName()) - .clientId(github.getClientId()) - .clientSecret(github.getClientSecret()) - .scope(github.getScopes()) - .authorizationUri(github.getAuthorizationuri()) - .tokenUri(github.getTokenuri()) - .userInfoUri(github.getUserinfouri()) - .userNameAttributeName(github.getUseAsUsername()) - .clientName(github.getClientName()) - .redirectUri("{baseUrl}/login/oauth2/code/" + github.getName()) - .authorizationGrantType( - org.springframework.security.oauth2.core - .AuthorizationGrantType.AUTHORIZATION_CODE) - .build()) - : Optional.empty(); - } + // @Bean + // public AuthenticationProvider daoAuthenticationProvider() { + // DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + // provider.setUserDetailsService(userDetailsService); // UserDetailsService + // provider.setPasswordEncoder(passwordEncoder()); // PasswordEncoder + // return provider; + // } - private Optional oidcClientRegistration() { - OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); - if (oauth == null - || oauth.getIssuer() == null - || oauth.getIssuer().isEmpty() - || oauth.getClientId() == null - || oauth.getClientId().isEmpty() - || oauth.getClientSecret() == null - || oauth.getClientSecret().isEmpty() - || oauth.getScopes() == null - || oauth.getScopes().isEmpty() - || oauth.getUseAsUsername() == null - || oauth.getUseAsUsername().isEmpty()) { - return Optional.empty(); - } - return Optional.of( - ClientRegistrations.fromIssuerLocation(oauth.getIssuer()) - .registrationId("oidc") - .clientId(oauth.getClientId()) - .clientSecret(oauth.getClientSecret()) - .scope(oauth.getScopes()) - .userNameAttributeName(oauth.getUseAsUsername()) - .clientName("OIDC") - .build()); - } - - /* - This following function is to grant Authorities to the OAUTH2 user from the values stored in the database. - This is required for the internal; 'hasRole()' function to give out the correct role. - */ @Bean - @ConditionalOnProperty( - value = "security.oauth2.enabled", - havingValue = "true", - matchIfMissing = false) - GrantedAuthoritiesMapper userAuthoritiesMapper() { - return (authorities) -> { - Set mappedAuthorities = new HashSet<>(); - - authorities.forEach( - authority -> { - // Add existing OAUTH2 Authorities - mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority())); - - // Add Authorities from database for existing user, if user is present. - if (authority instanceof OAuth2UserAuthority oauth2Auth) { - String useAsUsername = - applicationProperties - .getSecurity() - .getOauth2() - .getUseAsUsername(); - Optional userOpt = - userService.findByUsernameIgnoreCase( - (String) oauth2Auth.getAttributes().get(useAsUsername)); - if (userOpt.isPresent()) { - User user = userOpt.get(); - if (user != null) { - mappedAuthorities.add( - new SimpleGrantedAuthority( - userService.findRole(user).getAuthority())); - } - } - } - }); - return mappedAuthorities; - }; + public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { + AuthenticationManagerBuilder authenticationManagerBuilder = + http.getSharedObject(AuthenticationManagerBuilder.class); + + // authenticationManagerBuilder = + // authenticationManagerBuilder.authenticationProvider( + // daoAuthenticationProvider()); // Benutzername/Passwort + + if (applicationProperties.getSecurity().getSaml() != null + && applicationProperties.getSecurity().getSaml().getEnabled()) { + authenticationManagerBuilder.authenticationProvider( + samlAuthenticationProvider()); // SAML + } + return authenticationManagerBuilder.build(); } @Bean @@ -386,4 +281,14 @@ public PersistentTokenRepository persistentTokenRepository() { public boolean activSecurity() { return true; } + + // // Only Dev test + // @Bean + // public WebSecurityCustomizer webSecurityCustomizer() { + // return (web) -> + // web.ignoring() + // .requestMatchers( + // "/css/**", "/images/**", "/js/**", "/**.svg", + // "/pdfjs-legacy/**"); + // } } diff --git a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java index 79b285211d0..7b5bb47a843 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java @@ -5,7 +5,6 @@ import java.util.Optional; import java.util.stream.Collectors; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpStatus; @@ -30,13 +29,18 @@ @Component public class UserAuthenticationFilter extends OncePerRequestFilter { - @Autowired @Lazy private UserService userService; - - @Autowired private SessionPersistentRegistry sessionPersistentRegistry; - - @Autowired - @Qualifier("loginEnabled") - public boolean loginEnabledValue; + private final UserService userService; + private final SessionPersistentRegistry sessionPersistentRegistry; + private final boolean loginEnabledValue; + + public UserAuthenticationFilter( + @Lazy UserService userService, + SessionPersistentRegistry sessionPersistentRegistry, + @Qualifier("loginEnabled") boolean loginEnabledValue) { + this.userService = userService; + this.sessionPersistentRegistry = sessionPersistentRegistry; + this.loginEnabledValue = loginEnabledValue; + } @Override protected void doFilterInternal( @@ -51,6 +55,19 @@ protected void doFilterInternal( String requestURI = request.getRequestURI(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + // Check for session expiration (unsure if needed) + // if (authentication != null && authentication.isAuthenticated()) { + // String sessionId = request.getSession().getId(); + // SessionInformation sessionInfo = + // sessionPersistentRegistry.getSessionInformation(sessionId); + // + // if (sessionInfo != null && sessionInfo.isExpired()) { + // SecurityContextHolder.clearContext(); + // response.sendRedirect(request.getContextPath() + "/login?expired=true"); + // return; + // } + // } + // Check for API key in the request headers if no authentication exists if (authentication == null || !authentication.isAuthenticated()) { String apiKey = request.getHeader("X-API-Key"); diff --git a/src/main/java/stirling/software/SPDF/config/security/UserService.java b/src/main/java/stirling/software/SPDF/config/security/UserService.java index ece81355501..27e0baa2ec3 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserService.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserService.java @@ -19,7 +19,7 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; -import stirling.software.SPDF.config.DatabaseBackupInterface; +import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface; import stirling.software.SPDF.model.AuthenticationType; @@ -44,6 +44,10 @@ public class UserService implements UserServiceInterface { @Autowired DatabaseBackupInterface databaseBackupHelper; + public long getTotalUserCount() { + return userRepository.count(); + } + // Handle OAUTH2 login and user auto creation. public boolean processOAuth2PostLogin(String username, boolean autoCreateUser) throws IllegalArgumentException, IOException { diff --git a/src/main/java/stirling/software/SPDF/config/security/database/DatabaseBackupHelper.java b/src/main/java/stirling/software/SPDF/config/security/database/DatabaseBackupHelper.java index 3fd7993ff08..6ccd0ac3faf 100644 --- a/src/main/java/stirling/software/SPDF/config/security/database/DatabaseBackupHelper.java +++ b/src/main/java/stirling/software/SPDF/config/security/database/DatabaseBackupHelper.java @@ -24,7 +24,7 @@ import org.springframework.context.annotation.Configuration; import lombok.extern.slf4j.Slf4j; -import stirling.software.SPDF.config.DatabaseBackupInterface; +import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface; import stirling.software.SPDF.utils.FileInfo; @Slf4j diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/ConvertResponseToAuthentication.java b/src/main/java/stirling/software/SPDF/config/security/saml/ConvertResponseToAuthentication.java new file mode 100644 index 00000000000..02bf0593078 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/saml/ConvertResponseToAuthentication.java @@ -0,0 +1,68 @@ +package stirling.software.SPDF.config.security.saml; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.opensaml.saml.saml2.core.Assertion; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken; +import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import lombok.extern.slf4j.Slf4j; + +@Component +@Slf4j +public class ConvertResponseToAuthentication + implements Converter { + + private final Saml2AuthorityAttributeLookup saml2AuthorityAttributeLookup; + + public ConvertResponseToAuthentication( + Saml2AuthorityAttributeLookup saml2AuthorityAttributeLookup) { + this.saml2AuthorityAttributeLookup = saml2AuthorityAttributeLookup; + } + + @Override + public Saml2Authentication convert(ResponseToken responseToken) { + final Assertion assertion = + CollectionUtils.firstElement(responseToken.getResponse().getAssertions()); + final Map> attributes = + SamlAssertionUtils.getAssertionAttributes(assertion); + final String registrationId = + responseToken.getToken().getRelyingPartyRegistration().getRegistrationId(); + final ScimSaml2AuthenticatedPrincipal principal = + new ScimSaml2AuthenticatedPrincipal( + assertion, + attributes, + saml2AuthorityAttributeLookup.getIdentityMappings(registrationId)); + final Collection assertionAuthorities = + getAssertionAuthorities( + attributes, + saml2AuthorityAttributeLookup.getAuthorityAttribute(registrationId)); + return new Saml2Authentication( + principal, responseToken.getToken().getSaml2Response(), assertionAuthorities); + } + + private static Collection getAssertionAuthorities( + final Map> attributes, final String authoritiesAttributeName) { + if (attributes == null || attributes.isEmpty()) { + return Collections.emptySet(); + } + + final List groups = new ArrayList<>(attributes.get(authoritiesAttributeName)); + return groups.stream() + .filter(String.class::isInstance) + .map(String.class::cast) + .map(String::toLowerCase) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toSet()); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/CustomSAMLAuthenticationFailureHandler.java b/src/main/java/stirling/software/SPDF/config/security/saml/CustomSAMLAuthenticationFailureHandler.java new file mode 100644 index 00000000000..2a61e771f4d --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/saml/CustomSAMLAuthenticationFailureHandler.java @@ -0,0 +1,51 @@ +package stirling.software.SPDF.config.security.saml; + +import java.io.IOException; + +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.authentication.LockedException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class CustomSAMLAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { + + @Override + public void onAuthenticationFailure( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException exception) + throws IOException, ServletException { + + if (exception instanceof BadCredentialsException) { + log.error("BadCredentialsException", exception); + getRedirectStrategy().sendRedirect(request, response, "/login?error=badcredentials"); + return; + } + if (exception instanceof DisabledException) { + log.error("User is deactivated: ", exception); + getRedirectStrategy().sendRedirect(request, response, "/logout?userIsDisabled=true"); + return; + } + if (exception instanceof LockedException) { + log.error("Account locked: ", exception); + getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked"); + return; + } + if (exception instanceof Saml2AuthenticationException) { + log.error("SAML2 Authentication error: ", exception); + getRedirectStrategy() + .sendRedirect(request, response, "/logout?error=saml2AuthenticationError"); + return; + } + log.error("Unhandled authentication exception", exception); + super.onAuthenticationFailure(request, response, exception); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/CustomSAMLAuthenticationSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/saml/CustomSAMLAuthenticationSuccessHandler.java new file mode 100644 index 00000000000..46d20ac02eb --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/saml/CustomSAMLAuthenticationSuccessHandler.java @@ -0,0 +1,108 @@ +package stirling.software.SPDF.config.security.saml; + +import java.io.IOException; + +import org.springframework.security.authentication.LockedException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.security.web.savedrequest.SavedRequest; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import lombok.extern.slf4j.Slf4j; +import stirling.software.SPDF.config.security.LoginAttemptService; +import stirling.software.SPDF.config.security.UserService; +import stirling.software.SPDF.model.ApplicationProperties; +import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; +import stirling.software.SPDF.model.AuthenticationType; +import stirling.software.SPDF.utils.RequestUriUtils; + +@Slf4j +public class CustomSAMLAuthenticationSuccessHandler + extends SavedRequestAwareAuthenticationSuccessHandler { + + private LoginAttemptService loginAttemptService; + private UserService userService; + private ApplicationProperties applicationProperties; + + public CustomSAMLAuthenticationSuccessHandler( + LoginAttemptService loginAttemptService, + UserService userService, + ApplicationProperties applicationProperties) { + this.loginAttemptService = loginAttemptService; + this.userService = userService; + this.applicationProperties = applicationProperties; + } + + @Override + public void onAuthenticationSuccess( + HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws ServletException, IOException { + + Object principal = authentication.getPrincipal(); + String username = ""; + + if (principal instanceof OAuth2User) { + OAuth2User oauthUser = (OAuth2User) principal; + username = oauthUser.getName(); + } else if (principal instanceof UserDetails) { + UserDetails oauthUser = (UserDetails) principal; + username = oauthUser.getUsername(); + } else if (principal instanceof ScimSaml2AuthenticatedPrincipal) { + ScimSaml2AuthenticatedPrincipal samlPrincipal = + (ScimSaml2AuthenticatedPrincipal) principal; + username = samlPrincipal.getName(); + } + + // Get the saved request + HttpSession session = request.getSession(false); + String contextPath = request.getContextPath(); + SavedRequest savedRequest = + (session != null) + ? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST") + : null; + + if (savedRequest != null + && !RequestUriUtils.isStaticResource(contextPath, savedRequest.getRedirectUrl())) { + // Redirect to the original destination + super.onAuthenticationSuccess(request, response, authentication); + } else { + OAUTH2 oAuth = applicationProperties.getSecurity().getOauth2(); + + if (loginAttemptService.isBlocked(username)) { + if (session != null) { + session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST"); + } + throw new LockedException( + "Your account has been locked due to too many failed login attempts."); + } + if (userService.usernameExistsIgnoreCase(username) + && userService.hasPassword(username) + && !userService.isAuthenticationTypeByUsername( + username, AuthenticationType.OAUTH2) + && oAuth.getAutoCreateUser()) { + response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true"); + return; + } + try { + if (oAuth.getBlockRegistration() + && !userService.usernameExistsIgnoreCase(username)) { + response.sendRedirect(contextPath + "/logout?oauth2_admin_blocked_user=true"); + return; + } + if (principal instanceof OAuth2User) { + userService.processOAuth2PostLogin(username, oAuth.getAutoCreateUser()); + } + response.sendRedirect(contextPath + "/"); + return; + } catch (IllegalArgumentException e) { + response.sendRedirect(contextPath + "/logout?invalidUsername=true"); + return; + } + } + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/SAMLLogoutSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/saml/SAMLLogoutSuccessHandler.java new file mode 100644 index 00000000000..24e81889fe6 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/saml/SAMLLogoutSuccessHandler.java @@ -0,0 +1,38 @@ +package stirling.software.SPDF.config.security.saml; + +import java.io.IOException; + +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SAMLLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { + + @Override + public void onLogoutSuccess( + HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException { + + String redirectUrl = determineTargetUrl(request, response, authentication); + + if (response.isCommitted()) { + log.debug("Response has already been committed. Unable to redirect to " + redirectUrl); + return; + } + + getRedirectStrategy().sendRedirect(request, response, redirectUrl); + } + + protected String determineTargetUrl( + HttpServletRequest request, + HttpServletResponse response, + Authentication authentication) { + // Default to the root URL + return "/"; + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/Saml2AuthorityAttributeLookup.java b/src/main/java/stirling/software/SPDF/config/security/saml/Saml2AuthorityAttributeLookup.java new file mode 100644 index 00000000000..cceb5bf32c3 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/saml/Saml2AuthorityAttributeLookup.java @@ -0,0 +1,7 @@ +package stirling.software.SPDF.config.security.saml; + +public interface Saml2AuthorityAttributeLookup { + String getAuthorityAttribute(String registrationId); + + SimpleScimMappings getIdentityMappings(String registrationId); +} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/Saml2AuthorityAttributeLookupImpl.java b/src/main/java/stirling/software/SPDF/config/security/saml/Saml2AuthorityAttributeLookupImpl.java new file mode 100644 index 00000000000..c3b038e5834 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/saml/Saml2AuthorityAttributeLookupImpl.java @@ -0,0 +1,17 @@ +package stirling.software.SPDF.config.security.saml; + +import org.springframework.stereotype.Component; + +@Component +public class Saml2AuthorityAttributeLookupImpl implements Saml2AuthorityAttributeLookup { + + @Override + public String getAuthorityAttribute(String registrationId) { + return "authorityAttributeName"; + } + + @Override + public SimpleScimMappings getIdentityMappings(String registrationId) { + return new SimpleScimMappings(); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/SamlAssertionUtils.java b/src/main/java/stirling/software/SPDF/config/security/saml/SamlAssertionUtils.java new file mode 100644 index 00000000000..c4a560bf71a --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/saml/SamlAssertionUtils.java @@ -0,0 +1,63 @@ +package stirling.software.SPDF.config.security.saml; + +import java.time.Instant; +import java.util.*; + +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.schema.*; +import org.opensaml.saml.saml2.core.Assertion; + +public class SamlAssertionUtils { + + public static Map> getAssertionAttributes(Assertion assertion) { + Map> attributeMap = new LinkedHashMap<>(); + + assertion + .getAttributeStatements() + .forEach( + attributeStatement -> { + attributeStatement + .getAttributes() + .forEach( + attribute -> { + List attributeValues = new ArrayList<>(); + + attribute + .getAttributeValues() + .forEach( + xmlObject -> { + Object attributeValue = + getXmlObjectValue( + xmlObject); + if (attributeValue != null) { + attributeValues.add( + attributeValue); + } + }); + + attributeMap.put( + attribute.getName(), attributeValues); + }); + }); + + return attributeMap; + } + + public static Object getXmlObjectValue(XMLObject xmlObject) { + if (xmlObject instanceof XSAny) { + return ((XSAny) xmlObject).getTextContent(); + } else if (xmlObject instanceof XSString) { + return ((XSString) xmlObject).getValue(); + } else if (xmlObject instanceof XSInteger) { + return ((XSInteger) xmlObject).getValue(); + } else if (xmlObject instanceof XSURI) { + return ((XSURI) xmlObject).getURI(); + } else if (xmlObject instanceof XSBoolean) { + return ((XSBoolean) xmlObject).getValue().getValue(); + } else if (xmlObject instanceof XSDateTime) { + Instant dateTime = ((XSDateTime) xmlObject).getValue(); + return (dateTime != null) ? Instant.ofEpochMilli(dateTime.toEpochMilli()) : null; + } + return null; + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/SamlConfig.java b/src/main/java/stirling/software/SPDF/config/security/saml/SamlConfig.java new file mode 100644 index 00000000000..358c448866a --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/saml/SamlConfig.java @@ -0,0 +1,42 @@ +package stirling.software.SPDF.config.security.saml; + +import java.security.cert.CertificateException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; + +import lombok.extern.slf4j.Slf4j; +import stirling.software.SPDF.model.ApplicationProperties; + +@Configuration +@Slf4j +public class SamlConfig { + + @Autowired ApplicationProperties applicationProperties; + + @Bean + @ConditionalOnProperty( + value = "security.saml.enabled", + havingValue = "true", + matchIfMissing = false) + public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() + throws CertificateException { + RelyingPartyRegistration registration = + RelyingPartyRegistrations.fromMetadataLocation( + applicationProperties + .getSecurity() + .getSaml() + .getIdpMetadataLocation()) + .entityId(applicationProperties.getSecurity().getSaml().getEntityId()) + .registrationId( + applicationProperties.getSecurity().getSaml().getRegistrationId()) + .build(); + return new InMemoryRelyingPartyRegistrationRepository(registration); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/ScimSaml2AuthenticatedPrincipal.java b/src/main/java/stirling/software/SPDF/config/security/saml/ScimSaml2AuthenticatedPrincipal.java new file mode 100644 index 00000000000..9be94f389de --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/saml/ScimSaml2AuthenticatedPrincipal.java @@ -0,0 +1,89 @@ +package stirling.software.SPDF.config.security.saml; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.opensaml.saml.saml2.core.Assertion; +import org.springframework.security.core.AuthenticatedPrincipal; +import org.springframework.util.Assert; + +import com.unboundid.scim2.common.types.Email; +import com.unboundid.scim2.common.types.Name; +import com.unboundid.scim2.common.types.UserResource; + +public class ScimSaml2AuthenticatedPrincipal implements AuthenticatedPrincipal, Serializable { + + private static final long serialVersionUID = 1L; + + private final transient UserResource userResource; + + public ScimSaml2AuthenticatedPrincipal( + final Assertion assertion, + final Map> attributes, + final SimpleScimMappings attributeMappings) { + Assert.notNull(assertion, "assertion cannot be null"); + Assert.notNull(assertion.getSubject(), "assertion subject cannot be null"); + Assert.notNull( + assertion.getSubject().getNameID(), "assertion subject NameID cannot be null"); + Assert.notNull(attributes, "attributes cannot be null"); + Assert.notNull(attributeMappings, "attributeMappings cannot be null"); + + final Name name = + new Name() + .setFamilyName( + getAttribute( + attributes, + attributeMappings, + SimpleScimMappings::getFamilyName)) + .setGivenName( + getAttribute( + attributes, + attributeMappings, + SimpleScimMappings::getGivenName)); + + final List emails = new ArrayList<>(1); + emails.add( + new Email() + .setValue( + getAttribute( + attributes, + attributeMappings, + SimpleScimMappings::getEmail)) + .setPrimary(true)); + + userResource = + new UserResource() + .setUserName(assertion.getSubject().getNameID().getValue()) + .setName(name) + .setEmails(emails); + } + + private static String getAttribute( + final Map> attributes, + final SimpleScimMappings simpleScimMappings, + final Function attributeMapper) { + + final String key = attributeMapper.apply(simpleScimMappings); + + final List values = attributes.getOrDefault(key, Collections.emptyList()); + + return values.stream() + .filter(String.class::isInstance) + .map(String.class::cast) + .findFirst() + .orElse(null); + } + + @Override + public String getName() { + return this.userResource.getUserName(); + } + + public UserResource getUserResource() { + return this.userResource; + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/SimpleScimMappings.java b/src/main/java/stirling/software/SPDF/config/security/saml/SimpleScimMappings.java new file mode 100644 index 00000000000..97f61d3158e --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/saml/SimpleScimMappings.java @@ -0,0 +1,10 @@ +package stirling.software.SPDF.config.security.saml; + +import lombok.Data; + +@Data +public class SimpleScimMappings { + String givenName; + String familyName; + String email; +} diff --git a/src/main/java/stirling/software/SPDF/config/security/session/CustomHttpSessionListener.java b/src/main/java/stirling/software/SPDF/config/security/session/CustomHttpSessionListener.java index a4129e6a379..305684b94a5 100644 --- a/src/main/java/stirling/software/SPDF/config/security/session/CustomHttpSessionListener.java +++ b/src/main/java/stirling/software/SPDF/config/security/session/CustomHttpSessionListener.java @@ -11,16 +11,19 @@ @Slf4j public class CustomHttpSessionListener implements HttpSessionListener { - @Autowired private SessionPersistentRegistry sessionPersistentRegistry; + private SessionPersistentRegistry sessionPersistentRegistry; - @Override - public void sessionCreated(HttpSessionEvent se) { - log.info("Session created: " + se.getSession().getId()); + @Autowired + public CustomHttpSessionListener(SessionPersistentRegistry sessionPersistentRegistry) { + super(); + this.sessionPersistentRegistry = sessionPersistentRegistry; } + @Override + public void sessionCreated(HttpSessionEvent se) {} + @Override public void sessionDestroyed(HttpSessionEvent se) { - log.info("Session destroyed: " + se.getSession().getId()); sessionPersistentRegistry.expireSession(se.getSession().getId()); } } diff --git a/src/main/java/stirling/software/SPDF/config/security/session/SessionPersistentRegistry.java b/src/main/java/stirling/software/SPDF/config/security/session/SessionPersistentRegistry.java index bb63a8b5e2c..f7f78d1e192 100644 --- a/src/main/java/stirling/software/SPDF/config/security/session/SessionPersistentRegistry.java +++ b/src/main/java/stirling/software/SPDF/config/security/session/SessionPersistentRegistry.java @@ -84,6 +84,14 @@ public void registerNewSession(String sessionId, Object principal) { } if (principalName != null) { + // Clear old sessions for the principal (unsure if needed) + // List existingSessions = + // sessionRepository.findByPrincipalName(principalName); + // for (SessionEntity session : existingSessions) { + // session.setExpired(true); + // sessionRepository.save(session); + // } + SessionEntity sessionEntity = new SessionEntity(); sessionEntity.setSessionId(sessionId); sessionEntity.setPrincipalName(principalName); diff --git a/src/main/java/stirling/software/SPDF/controller/api/CropController.java b/src/main/java/stirling/software/SPDF/controller/api/CropController.java index 551cd72dae6..d386d1ce575 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/CropController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/CropController.java @@ -25,6 +25,7 @@ import stirling.software.SPDF.model.api.general.CropPdfForm; import stirling.software.SPDF.service.CustomPDDocumentFactory; +import stirling.software.SPDF.service.PostHogService; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -36,9 +37,13 @@ public class CropController { private final CustomPDDocumentFactory pdfDocumentFactory; + private final PostHogService postHogService; + @Autowired - public CropController(CustomPDDocumentFactory pdfDocumentFactory) { + public CropController( + CustomPDDocumentFactory pdfDocumentFactory, PostHogService postHogService) { this.pdfDocumentFactory = pdfDocumentFactory; + this.postHogService = postHogService; } @PostMapping(value = "/crop", consumes = "multipart/form-data") diff --git a/src/main/java/stirling/software/SPDF/controller/api/SettingsController.java b/src/main/java/stirling/software/SPDF/controller/api/SettingsController.java new file mode 100644 index 00000000000..dd6742108ad --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/SettingsController.java @@ -0,0 +1,37 @@ +package stirling.software.SPDF.controller.api; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.tags.Tag; + +import stirling.software.SPDF.model.ApplicationProperties; +import stirling.software.SPDF.utils.GeneralUtils; + +@Controller +@Tag(name = "Settings", description = "Settings APIs") +@RequestMapping("/api/v1/settings") +@Hidden +public class SettingsController { + + @Autowired ApplicationProperties applicationProperties; + + @PostMapping("/update-enable-analytics") + @Hidden + public ResponseEntity updateApiKey(@RequestBody Boolean enabled) throws IOException { + if (!"undefined".equals(applicationProperties.getSystem().getEnableAnalytics())) { + return ResponseEntity.status(HttpStatus.ALREADY_REPORTED) + .body( + "Setting has already been set, To adjust please edit /config/settings.yml"); + } + GeneralUtils.saveKeyToConfig("system.enableAnalytics", String.valueOf(enabled), false); + applicationProperties.getSystem().setEnableAnalytics(String.valueOf(enabled)); + return ResponseEntity.ok("Updated"); + } +} diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java index 2da76fa363e..e27df103374 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java @@ -60,8 +60,6 @@ public ResponseEntity splitPdf(@ModelAttribute PDFWithPageNums request) // PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document); int totalPages = document.getNumberOfPages(); List pageNumbers = request.getPageNumbersList(document, false); - System.out.println( - pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(","))); if (!pageNumbers.contains(totalPages - 1)) { // Create a mutable ArrayList so we can add to it pageNumbers = new ArrayList<>(pageNumbers); diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfByChaptersController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfByChaptersController.java index 0cca8f7886a..f344c2763e0 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfByChaptersController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfByChaptersController.java @@ -32,9 +32,9 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -import stirling.software.SPDF.config.PdfMetadataService; import stirling.software.SPDF.model.PdfMetadata; import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest; +import stirling.software.SPDF.service.PdfMetadataService; import stirling.software.SPDF.utils.WebResponseUtils; @RestController diff --git a/src/main/java/stirling/software/SPDF/controller/api/UserController.java b/src/main/java/stirling/software/SPDF/controller/api/UserController.java index 719adac19a5..cc928749681 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/UserController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/UserController.java @@ -30,6 +30,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.config.security.UserService; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.model.AuthenticationType; @@ -40,6 +41,7 @@ @Controller @Tag(name = "User", description = "User APIs") @RequestMapping("/api/v1/user") +@Slf4j public class UserController { @Autowired private UserService userService; @@ -191,13 +193,11 @@ public String updateUserSettings(HttpServletRequest request, Principal principal Map paramMap = request.getParameterMap(); Map updates = new HashMap<>(); - System.out.println("Received parameter map: " + paramMap); - for (Map.Entry entry : paramMap.entrySet()) { updates.put(entry.getKey(), entry.getValue()[0]); } - System.out.println("Processed updates: " + updates); + log.debug("Processed updates: " + updates); // Assuming you have a method in userService to update the settings for a user userService.updateUserSettings(principal.getName(), updates); @@ -209,7 +209,7 @@ public String updateUserSettings(HttpServletRequest request, Principal principal @PostMapping("/admin/saveUser") public RedirectView saveUser( @RequestParam(name = "username", required = true) String username, - @RequestParam(name = "password", required = true) String password, + @RequestParam(name = "password", required = false) String password, @RequestParam(name = "role") String role, @RequestParam(name = "authType") String authType, @RequestParam(name = "forceChange", required = false, defaultValue = "false") diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertBookToPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertBookToPDFController.java index 694d30ab881..778aab0473c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertBookToPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertBookToPDFController.java @@ -1,5 +1,6 @@ package stirling.software.SPDF.controller.api.converters; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -14,7 +15,6 @@ import stirling.software.SPDF.utils.FileToPdf; import stirling.software.SPDF.utils.WebResponseUtils; -// Disabled for now // @RestController // @Tag(name = "Convert", description = "Convert APIs") // @RequestMapping("/api/v1/convert") @@ -24,7 +24,7 @@ public class ConvertBookToPDFController { private final CustomPDDocumentFactory pdfDocumentFactory; - // @Autowired + @Autowired public ConvertBookToPDFController( CustomPDDocumentFactory pdfDocumentFactory, @Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) { @@ -66,6 +66,8 @@ public ResponseEntity HtmlToPdf(@ModelAttribute GeneralFile request) thr } byte[] pdfBytes = FileToPdf.convertBookTypeToPdf(fileInput.getBytes(), originalFilename); + pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes); + String outputFilename = originalFilename.replaceFirst("[.][^.]+$", "") + ".pdf"; // Remove file extension and append .pdf diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java index 19ba1ac43aa..a4fe57bb99c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java @@ -1,27 +1,39 @@ package stirling.software.SPDF.controller.api.converters; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.github.pixee.security.Filenames; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.FileToPdf; import stirling.software.SPDF.utils.WebResponseUtils; -// Disabled for now -// @RestController -// @Tag(name = "Convert", description = "Convert APIs") -// @RequestMapping("/api/v1/convert") +@RestController +@Tag(name = "Convert", description = "Convert APIs") +@RequestMapping("/api/v1/convert") public class ConvertHtmlToPDF { - // @Autowired - @Qualifier("bookAndHtmlFormatsInstalled") - private boolean bookAndHtmlFormatsInstalled; + private final boolean bookAndHtmlFormatsInstalled; + + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public ConvertHtmlToPDF( + CustomPDDocumentFactory pdfDocumentFactory, + @Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) { + this.pdfDocumentFactory = pdfDocumentFactory; + this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled; + } @PostMapping(consumes = "multipart/form-data", value = "/html/pdf") @Operation( @@ -49,6 +61,8 @@ public ResponseEntity HtmlToPdf(@ModelAttribute HTMLToPdfRequest request originalFilename, bookAndHtmlFormatsInstalled); + pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes); + String outputFilename = originalFilename.replaceFirst("[.][^.]+$", "") + ".pdf"; // Remove file extension and append .pdf diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java index 5b2f3fdf5b1..b378f479fdf 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java @@ -10,28 +10,40 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.html.AttributeProvider; import org.commonmark.renderer.html.HtmlRenderer; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.github.pixee.security.Filenames; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.GeneralFile; +import stirling.software.SPDF.service.CustomPDDocumentFactory; import stirling.software.SPDF.utils.FileToPdf; import stirling.software.SPDF.utils.WebResponseUtils; -// Disabled for now -// @RestController -// @Tag(name = "Convert", description = "Convert APIs") -// @RequestMapping("/api/v1/convert") +@RestController +@Tag(name = "Convert", description = "Convert APIs") +@RequestMapping("/api/v1/convert") public class ConvertMarkdownToPdf { - // @Autowired - @Qualifier("bookAndHtmlFormatsInstalled") - private boolean bookAndHtmlFormatsInstalled; + private final boolean bookAndHtmlFormatsInstalled; + + private final CustomPDDocumentFactory pdfDocumentFactory; + + @Autowired + public ConvertMarkdownToPdf( + CustomPDDocumentFactory pdfDocumentFactory, + @Qualifier("bookAndHtmlFormatsInstalled") boolean bookAndHtmlFormatsInstalled) { + this.pdfDocumentFactory = pdfDocumentFactory; + this.bookAndHtmlFormatsInstalled = bookAndHtmlFormatsInstalled; + } @PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf") @Operation( @@ -70,7 +82,7 @@ public ResponseEntity markdownToPdf(@ModelAttribute GeneralFile request) htmlContent.getBytes(), "converted.html", bookAndHtmlFormatsInstalled); - + pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes); String outputFilename = originalFilename.replaceFirst("[.][^.]+$", "") + ".pdf"; // Remove file extension and append .pdf diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToBookController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToBookController.java index 181b5713ad8..f1c672e12be 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToBookController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToBookController.java @@ -6,6 +6,7 @@ import java.util.Arrays; import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; @@ -20,13 +21,12 @@ import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; -// Disabled for now // @RestController // @Tag(name = "Convert", description = "Convert APIs") // @RequestMapping("/api/v1/convert") public class ConvertPDFToBookController { - // @Autowired + @Autowired @Qualifier("bookAndHtmlFormatsInstalled") private boolean bookAndHtmlFormatsInstalled; diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java index 14aef73462f..c437c4cb0a0 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java @@ -61,7 +61,7 @@ public ResponseEntity pdfToPdfA(@ModelAttribute PdfToPdfARequest request command.add("-dPDFA=" + ("pdfa".equals(outputFormat) ? "2" : "1")); command.add("-dNOPAUSE"); command.add("-dBATCH"); - command.add("-sColorConversionStrategy=UseDeviceIndependentColor"); + command.add("-sColorConversionStrategy=sRGB"); command.add("-sDEVICE=pdfwrite"); command.add("-dPDFACompatibilityPolicy=2"); command.add("-o"); diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java index 3dff43e8492..075de0d143e 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java @@ -60,8 +60,6 @@ public ResponseEntity extractImages(@ModelAttribute PDFExtractImagesRequ MultipartFile file = request.getFileInput(); String format = request.getFormat(); boolean allowDuplicates = request.isAllowDuplicates(); - System.out.println( - System.currentTimeMillis() + " file=" + file.getName() + ", format=" + format); PDDocument document = Loader.loadPDF(file.getBytes()); // Determine if multithreading should be used based on PDF size or number of pages diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/PrintFileController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/PrintFileController.java index bc0a6715f82..8f993abd054 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/PrintFileController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/PrintFileController.java @@ -26,11 +26,13 @@ import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.model.api.misc.PrintFileRequest; @RestController @RequestMapping("/api/v1/misc") @Tag(name = "Misc", description = "Miscellaneous APIs") +@Slf4j public class PrintFileController { // TODO @@ -59,7 +61,7 @@ public ResponseEntity printFile(@ModelAttribute PrintFileRequest request new IllegalArgumentException( "No matching printer found")); - System.out.println("Selected Printer: " + selectedService.getName()); + log.info("Selected Printer: " + selectedService.getName()); if ("application/pdf".equals(contentType)) { PDDocument document = Loader.loadPDF(file.getBytes()); diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java b/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java index 40dc2c75de3..d9e1e4ca976 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/RedactController.java @@ -58,7 +58,6 @@ public ResponseEntity redactPdf(@ModelAttribute RedactPdfRequest request float customPadding = request.getCustomPadding(); boolean convertPDFToImage = request.isConvertPDFToImage(); - System.out.println(listOfTextString); String[] listOfText = listOfTextString.split("\n"); PDDocument document = pdfDocumentFactory.load(file); @@ -75,7 +74,6 @@ public ResponseEntity redactPdf(@ModelAttribute RedactPdfRequest request for (String text : listOfText) { text = text.trim(); - System.out.println(text); TextFinder textFinder = new TextFinder(text, useRegex, wholeWordSearchBool); List foundTexts = textFinder.getTextLocations(document); redactFoundText(document, foundTexts, customPadding, redactColor); diff --git a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java index 93ad5f342e0..14af6ee7a5a 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java @@ -108,6 +108,13 @@ public String splitPdfBySections(Model model) { return "split-pdf-by-sections"; } + @GetMapping("/split-pdf-by-chapters") + @Hidden + public String splitPdfByChapters(Model model) { + model.addAttribute("currentPage", "split-pdf-by-chapters"); + return "split-pdf-by-chapters"; + } + @GetMapping("/view-pdf") @Hidden public String ViewPdfForm2(Model model) { diff --git a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java index 46c92633a48..10ad434aa0c 100644 --- a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java +++ b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java @@ -11,6 +11,11 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; import lombok.Data; import lombok.ToString; @@ -24,6 +29,7 @@ @ConfigurationProperties(prefix = "") @PropertySource(value = "file:./configs/settings.yml", factory = YamlPropertySourceFactory.class) @Data +@Order(Ordered.HIGHEST_PRECEDENCE) public class ApplicationProperties { private Legal legal = new Legal(); @@ -57,6 +63,7 @@ public static class Security { private Boolean csrfDisabled; private InitialLogin initialLogin = new InitialLogin(); private OAUTH2 oauth2 = new OAUTH2(); + private SAML saml = new SAML(); private int loginAttemptCount; private long loginResetTimeMinutes; private String loginMethod = "all"; @@ -67,6 +74,34 @@ public static class InitialLogin { @ToString.Exclude private String password; } + @Data + public static class SAML { + private Boolean enabled = false; + private String entityId; + private String registrationId; + private String spBaseUrl; + private String idpMetadataLocation; + private KeyStore keystore; + + @Data + public static class KeyStore { + private String keystoreLocation; + private String keystorePassword; + private String keyAlias; + private String keyPassword; + private String realmCertificateAlias; + + public Resource getKeystoreResource() { + if (keystoreLocation.startsWith("classpath:")) { + return new ClassPathResource( + keystoreLocation.substring("classpath:".length())); + } else { + return new FileSystemResource(keystoreLocation); + } + } + } + } + @Data public static class OAUTH2 { private Boolean enabled = false; @@ -136,6 +171,7 @@ public static class System { private boolean customHTMLFiles; private String tessdataDir; private Boolean enableAlphaFunctionality; + private String enableAnalytics; } @Data @@ -175,11 +211,14 @@ public static class Metrics { @Data public static class AutomaticallyGenerated { @ToString.Exclude private String key; + private String UUID; } @Data public static class EnterpriseEdition { + private boolean enabled; @ToString.Exclude private String key; + private int maxUsers; private CustomMetadata customMetadata = new CustomMetadata(); @Data diff --git a/src/main/java/stirling/software/SPDF/pdf/TextFinder.java b/src/main/java/stirling/software/SPDF/pdf/TextFinder.java index f9e339c2fa4..77f7d0c6f47 100644 --- a/src/main/java/stirling/software/SPDF/pdf/TextFinder.java +++ b/src/main/java/stirling/software/SPDF/pdf/TextFinder.java @@ -10,8 +10,10 @@ import org.apache.pdfbox.text.PDFTextStripper; import org.apache.pdfbox.text.TextPosition; +import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.model.PDFText; +@Slf4j public class TextFinder extends PDFTextStripper { private final String searchText; @@ -92,7 +94,7 @@ protected void writeString(String text, List textPositions) { public List getTextLocations(PDDocument document) throws Exception { this.getText(document); - System.out.println( + log.debug( "Found " + textOccurrences.size() + " occurrences of '" diff --git a/src/main/java/stirling/software/SPDF/service/CustomPDDocumentFactory.java b/src/main/java/stirling/software/SPDF/service/CustomPDDocumentFactory.java index 2d3d8990e0b..0807e0d110e 100644 --- a/src/main/java/stirling/software/SPDF/service/CustomPDDocumentFactory.java +++ b/src/main/java/stirling/software/SPDF/service/CustomPDDocumentFactory.java @@ -13,7 +13,6 @@ import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; -import stirling.software.SPDF.config.PdfMetadataService; import stirling.software.SPDF.model.PdfMetadata; import stirling.software.SPDF.model.api.PDFFile; @@ -35,6 +34,36 @@ public PDDocument createNewDocument() throws IOException { return document; } + public byte[] createNewBytesBasedOnOldDocument(byte[] oldDocument) throws IOException { + PDDocument document = Loader.loadPDF(oldDocument); + return createNewBytesBasedOnOldDocument(document); + } + + public byte[] createNewBytesBasedOnOldDocument(File oldDocument) throws IOException { + PDDocument document = Loader.loadPDF(oldDocument); + return createNewBytesBasedOnOldDocument(document); + } + + public byte[] createNewBytesBasedOnOldDocument(PDDocument oldDocument) throws IOException { + pdfMetadataService.setMetadataToPdf( + oldDocument, pdfMetadataService.extractMetadataFromPdf(oldDocument), true); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + oldDocument.save(baos); + oldDocument.close(); + return baos.toByteArray(); + } + + public PDDocument createNewDocumentBasedOnOldDocument(byte[] oldDocument) throws IOException { + PDDocument document = Loader.loadPDF(oldDocument); + return createNewDocumentBasedOnOldDocument(document); + } + + public PDDocument createNewDocumentBasedOnOldDocument(File oldDocument) throws IOException { + PDDocument document = Loader.loadPDF(oldDocument); + return createNewDocumentBasedOnOldDocument(document); + } + public PDDocument createNewDocumentBasedOnOldDocument(PDDocument oldDocument) throws IOException { PDDocument document = new PDDocument(); diff --git a/src/main/java/stirling/software/SPDF/service/MetricsAggregatorService.java b/src/main/java/stirling/software/SPDF/service/MetricsAggregatorService.java new file mode 100644 index 00000000000..eb3a83b0165 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/service/MetricsAggregatorService.java @@ -0,0 +1,56 @@ +package stirling.software.SPDF.service; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.search.Search; + +@Service +public class MetricsAggregatorService { + + private final MeterRegistry meterRegistry; + private final PostHogService postHogService; + private final Map lastSentMetrics = new ConcurrentHashMap<>(); + + @Autowired + public MetricsAggregatorService(MeterRegistry meterRegistry, PostHogService postHogService) { + this.meterRegistry = meterRegistry; + this.postHogService = postHogService; + } + + @Scheduled(fixedRate = 900000) // Run every 15 minutes + public void aggregateAndSendMetrics() { + Map metrics = new HashMap<>(); + Search.in(meterRegistry) + .name("http.requests") + .counters() + .forEach( + counter -> { + String key = + String.format( + "http_requests_%s_%s", + counter.getId().getTag("method"), + counter.getId().getTag("uri").replace("/", "_")); + + double currentCount = counter.count(); + double lastCount = lastSentMetrics.getOrDefault(key, 0.0); + double difference = currentCount - lastCount; + + if (difference > 0) { + metrics.put(key, difference); + lastSentMetrics.put(key, currentCount); + } + }); + + // Send aggregated metrics to PostHog + if (!metrics.isEmpty()) { + postHogService.captureEvent("aggregated_metrics", metrics); + } + } +} diff --git a/src/main/java/stirling/software/SPDF/config/PdfMetadataService.java b/src/main/java/stirling/software/SPDF/service/PdfMetadataService.java similarity index 62% rename from src/main/java/stirling/software/SPDF/config/PdfMetadataService.java rename to src/main/java/stirling/software/SPDF/service/PdfMetadataService.java index 7b40a878aed..373b4c916ff 100644 --- a/src/main/java/stirling/software/SPDF/config/PdfMetadataService.java +++ b/src/main/java/stirling/software/SPDF/service/PdfMetadataService.java @@ -1,4 +1,4 @@ -package stirling.software.SPDF.config; +package stirling.software.SPDF.service; import java.util.Calendar; @@ -15,16 +15,16 @@ public class PdfMetadataService { private final ApplicationProperties applicationProperties; - private final String appVersion; + private final String stirlingPDFLabel; private final UserServiceInterface userService; @Autowired public PdfMetadataService( ApplicationProperties applicationProperties, - @Qualifier("appVersion") String appVersion, + @Qualifier("StirlingPDFLabel") String stirlingPDFLabel, @Autowired(required = false) UserServiceInterface userService) { this.applicationProperties = applicationProperties; - this.appVersion = appVersion; + this.stirlingPDFLabel = stirlingPDFLabel; this.userService = userService; } @@ -59,51 +59,40 @@ public void setMetadataToPdf(PDDocument pdf, PdfMetadata pdfMetadata, boolean ne private void setNewDocumentMetadata(PDDocument pdf, PdfMetadata pdfMetadata) { - String creator = "Stirling-PDF"; + String creator = stirlingPDFLabel; - // if (applicationProperties - // .getEnterpriseEdition() - // .getCustomMetadata() - // .isAutoUpdateMetadata()) { + if (applicationProperties + .getEnterpriseEdition() + .getCustomMetadata() + .isAutoUpdateMetadata()) { - // producer = - // - // applicationProperties.getEnterpriseEdition().getCustomMetadata().getProducer(); - // creator = - // applicationProperties.getEnterpriseEdition().getCustomMetadata().getCreator(); - // title = applicationProperties.getEnterpriseEdition().getCustomMetadata().getTitle(); - - // if ("{filename}".equals(title)) { - // title = "Filename"; // Replace with actual filename logic - // } else if ("{unchanged}".equals(title)) { - // title = pdfMetadata.getTitle(); // Keep the original title - // } - // } + creator = applicationProperties.getEnterpriseEdition().getCustomMetadata().getCreator(); + pdf.getDocumentInformation().setProducer(stirlingPDFLabel); + } - pdf.getDocumentInformation().setCreator(creator + " " + appVersion); + pdf.getDocumentInformation().setCreator(creator); pdf.getDocumentInformation().setCreationDate(Calendar.getInstance()); } private void setCommonMetadata(PDDocument pdf, PdfMetadata pdfMetadata) { - String producer = "Stirling-PDF"; String title = pdfMetadata.getTitle(); pdf.getDocumentInformation().setTitle(title); - pdf.getDocumentInformation().setProducer(producer + " " + appVersion); + pdf.getDocumentInformation().setProducer(stirlingPDFLabel); pdf.getDocumentInformation().setSubject(pdfMetadata.getSubject()); pdf.getDocumentInformation().setKeywords(pdfMetadata.getKeywords()); pdf.getDocumentInformation().setModificationDate(Calendar.getInstance()); String author = pdfMetadata.getAuthor(); - // if (applicationProperties - // .getEnterpriseEdition() - // .getCustomMetadata() - // .isAutoUpdateMetadata()) { - // author = applicationProperties.getEnterpriseEdition().getCustomMetadata().getAuthor(); - - // if (userService != null) { - // author = author.replace("username", userService.getCurrentUsername()); - // } - // } + if (applicationProperties + .getEnterpriseEdition() + .getCustomMetadata() + .isAutoUpdateMetadata()) { + author = applicationProperties.getEnterpriseEdition().getCustomMetadata().getAuthor(); + + if (userService != null) { + author = author.replace("username", userService.getCurrentUsername()); + } + } pdf.getDocumentInformation().setAuthor(author); } } diff --git a/src/main/java/stirling/software/SPDF/service/PostHogService.java b/src/main/java/stirling/software/SPDF/service/PostHogService.java new file mode 100644 index 00000000000..9b22cf1cbc5 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/service/PostHogService.java @@ -0,0 +1,379 @@ +package stirling.software.SPDF.service; + +import java.io.File; +import java.lang.management.*; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +import com.posthog.java.PostHog; + +import stirling.software.SPDF.model.ApplicationProperties; + +@Service +public class PostHogService { + private final PostHog postHog; + private final String uniqueId; + private final ApplicationProperties applicationProperties; + + @Autowired + public PostHogService( + PostHog postHog, + @Qualifier("UUID") String uuid, + ApplicationProperties applicationProperties) { + this.postHog = postHog; + this.uniqueId = uuid; + this.applicationProperties = applicationProperties; + captureSystemInfo(); + } + + private void captureSystemInfo() { + if (!Boolean.getBoolean(applicationProperties.getSystem().getEnableAnalytics())) { + return; + } + try { + postHog.capture(uniqueId, "system_info_captured", captureServerMetrics()); + } catch (Exception e) { + // Handle exceptions + } + } + + public void captureEvent(String eventName, Map properties) { + if (!Boolean.getBoolean(applicationProperties.getSystem().getEnableAnalytics())) { + return; + } + postHog.capture(uniqueId, eventName, properties); + } + + public Map captureServerMetrics() { + Map metrics = new HashMap<>(); + + try { + // System info + metrics.put("os_name", System.getProperty("os.name")); + metrics.put("os_version", System.getProperty("os.version")); + metrics.put("java_version", System.getProperty("java.version")); + metrics.put("user_name", System.getProperty("user.name")); + metrics.put("user_home", System.getProperty("user.home")); + metrics.put("user_dir", System.getProperty("user.dir")); + + // CPU and Memory + metrics.put("cpu_cores", Runtime.getRuntime().availableProcessors()); + metrics.put("total_memory", Runtime.getRuntime().totalMemory()); + metrics.put("free_memory", Runtime.getRuntime().freeMemory()); + + // Network and Server Identity + InetAddress localHost = InetAddress.getLocalHost(); + metrics.put("ip_address", localHost.getHostAddress()); + metrics.put("hostname", localHost.getHostName()); + metrics.put("mac_address", getMacAddress()); + + // JVM info + metrics.put("jvm_vendor", System.getProperty("java.vendor")); + metrics.put("jvm_version", System.getProperty("java.vm.version")); + + // Locale and Timezone + metrics.put("system_language", System.getProperty("user.language")); + metrics.put("system_country", System.getProperty("user.country")); + metrics.put("timezone", TimeZone.getDefault().getID()); + metrics.put("locale", Locale.getDefault().toString()); + + // Disk info + File root = new File("."); + metrics.put("total_disk_space", root.getTotalSpace()); + metrics.put("free_disk_space", root.getFreeSpace()); + + // Process info + metrics.put("process_id", ProcessHandle.current().pid()); + + // JVM metrics + RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); + metrics.put("jvm_uptime_ms", runtimeMXBean.getUptime()); + metrics.put("jvm_start_time", runtimeMXBean.getStartTime()); + + // Memory metrics + MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + metrics.put("heap_memory_usage", memoryMXBean.getHeapMemoryUsage().getUsed()); + metrics.put("non_heap_memory_usage", memoryMXBean.getNonHeapMemoryUsage().getUsed()); + + // CPU metrics + OperatingSystemMXBean osMXBean = ManagementFactory.getOperatingSystemMXBean(); + metrics.put("system_load_average", osMXBean.getSystemLoadAverage()); + + // Thread metrics + ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + metrics.put("thread_count", threadMXBean.getThreadCount()); + metrics.put("daemon_thread_count", threadMXBean.getDaemonThreadCount()); + metrics.put("peak_thread_count", threadMXBean.getPeakThreadCount()); + + // Garbage collection metrics + for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) { + metrics.put("gc_" + gcBean.getName() + "_count", gcBean.getCollectionCount()); + metrics.put("gc_" + gcBean.getName() + "_time", gcBean.getCollectionTime()); + } + + // Network interfaces + metrics.put("network_interfaces", getNetworkInterfacesInfo()); + + // Docker detection and stats + boolean isDocker = isRunningInDocker(); + metrics.put("is_docker", isDocker); + if (isDocker) { + metrics.put("docker_metrics", getDockerMetrics()); + } + metrics.put("application_properties", captureApplicationProperties()); + + } catch (Exception e) { + metrics.put("error", e.getMessage()); + } + + return metrics; + } + + private boolean isRunningInDocker() { + return Files.exists(Paths.get("/.dockerenv")); + } + + private Map getDockerMetrics() { + Map dockerMetrics = new HashMap<>(); + + // Network-related Docker info + dockerMetrics.put("docker_network_mode", System.getenv("DOCKER_NETWORK_MODE")); + + // Container name (if set) + String containerName = System.getenv("CONTAINER_NAME"); + if (containerName != null && !containerName.isEmpty()) { + dockerMetrics.put("container_name", containerName); + } + + // Docker compose information + String composeProject = System.getenv("COMPOSE_PROJECT_NAME"); + String composeService = System.getenv("COMPOSE_SERVICE_NAME"); + if (composeProject != null && composeService != null) { + dockerMetrics.put("compose_project", composeProject); + dockerMetrics.put("compose_service", composeService); + } + + // Kubernetes-specific info (if running in K8s) + String k8sPodName = System.getenv("KUBERNETES_POD_NAME"); + if (k8sPodName != null) { + dockerMetrics.put("k8s_pod_name", k8sPodName); + dockerMetrics.put("k8s_namespace", System.getenv("KUBERNETES_NAMESPACE")); + dockerMetrics.put("k8s_node_name", System.getenv("KUBERNETES_NODE_NAME")); + } + + // New environment variables + dockerMetrics.put("version_tag", System.getenv("VERSION_TAG")); + dockerMetrics.put("docker_enable_security", System.getenv("DOCKER_ENABLE_SECURITY")); + dockerMetrics.put("fat_docker", System.getenv("FAT_DOCKER")); + + return dockerMetrics; + } + + private void addIfNotEmpty(Map map, String key, Object value) { + if (value != null) { + if (value instanceof String) { + String strValue = (String) value; + if (!StringUtils.isBlank(strValue)) { + map.put(key, strValue.trim()); + } + } else { + map.put(key, value); + } + } + } + + public Map captureApplicationProperties() { + Map properties = new HashMap<>(); + + // Capture Legal properties + addIfNotEmpty( + properties, + "legal_termsAndConditions", + applicationProperties.getLegal().getTermsAndConditions()); + addIfNotEmpty( + properties, + "legal_privacyPolicy", + applicationProperties.getLegal().getPrivacyPolicy()); + addIfNotEmpty( + properties, + "legal_accessibilityStatement", + applicationProperties.getLegal().getAccessibilityStatement()); + addIfNotEmpty( + properties, + "legal_cookiePolicy", + applicationProperties.getLegal().getCookiePolicy()); + addIfNotEmpty( + properties, "legal_impressum", applicationProperties.getLegal().getImpressum()); + + // Capture Security properties + addIfNotEmpty( + properties, + "security_enableLogin", + applicationProperties.getSecurity().getEnableLogin()); + addIfNotEmpty( + properties, + "security_csrfDisabled", + applicationProperties.getSecurity().getCsrfDisabled()); + addIfNotEmpty( + properties, + "security_loginAttemptCount", + applicationProperties.getSecurity().getLoginAttemptCount()); + addIfNotEmpty( + properties, + "security_loginResetTimeMinutes", + applicationProperties.getSecurity().getLoginResetTimeMinutes()); + addIfNotEmpty( + properties, + "security_loginMethod", + applicationProperties.getSecurity().getLoginMethod()); + + // Capture OAuth2 properties (excluding sensitive information) + addIfNotEmpty( + properties, + "security_oauth2_enabled", + applicationProperties.getSecurity().getOauth2().getEnabled()); + if (applicationProperties.getSecurity().getOauth2().getEnabled()) { + addIfNotEmpty( + properties, + "security_oauth2_autoCreateUser", + applicationProperties.getSecurity().getOauth2().getAutoCreateUser()); + addIfNotEmpty( + properties, + "security_oauth2_blockRegistration", + applicationProperties.getSecurity().getOauth2().getBlockRegistration()); + addIfNotEmpty( + properties, + "security_oauth2_useAsUsername", + applicationProperties.getSecurity().getOauth2().getUseAsUsername()); + addIfNotEmpty( + properties, + "security_oauth2_provider", + applicationProperties.getSecurity().getOauth2().getProvider()); + } + // Capture System properties + addIfNotEmpty( + properties, + "system_defaultLocale", + applicationProperties.getSystem().getDefaultLocale()); + addIfNotEmpty( + properties, + "system_googlevisibility", + applicationProperties.getSystem().getGooglevisibility()); + addIfNotEmpty( + properties, "system_showUpdate", applicationProperties.getSystem().isShowUpdate()); + addIfNotEmpty( + properties, + "system_showUpdateOnlyAdmin", + applicationProperties.getSystem().getShowUpdateOnlyAdmin()); + addIfNotEmpty( + properties, + "system_customHTMLFiles", + applicationProperties.getSystem().isCustomHTMLFiles()); + addIfNotEmpty( + properties, + "system_tessdataDir", + applicationProperties.getSystem().getTessdataDir()); + addIfNotEmpty( + properties, + "system_enableAlphaFunctionality", + applicationProperties.getSystem().getEnableAlphaFunctionality()); + addIfNotEmpty( + properties, + "system_enableAnalytics", + applicationProperties.getSystem().getEnableAnalytics()); + + // Capture UI properties + addIfNotEmpty(properties, "ui_appName", applicationProperties.getUi().getAppName()); + addIfNotEmpty( + properties, + "ui_homeDescription", + applicationProperties.getUi().getHomeDescription()); + addIfNotEmpty( + properties, "ui_appNameNavbar", applicationProperties.getUi().getAppNameNavbar()); + + // Capture Metrics properties + addIfNotEmpty( + properties, "metrics_enabled", applicationProperties.getMetrics().getEnabled()); + + // Capture EnterpriseEdition properties + addIfNotEmpty( + properties, + "enterpriseEdition_enabled", + applicationProperties.getEnterpriseEdition().isEnabled()); + if (applicationProperties.getEnterpriseEdition().isEnabled()) { + addIfNotEmpty( + properties, + "enterpriseEdition_customMetadata_autoUpdateMetadata", + applicationProperties + .getEnterpriseEdition() + .getCustomMetadata() + .isAutoUpdateMetadata()); + addIfNotEmpty( + properties, + "enterpriseEdition_customMetadata_author", + applicationProperties.getEnterpriseEdition().getCustomMetadata().getAuthor()); + addIfNotEmpty( + properties, + "enterpriseEdition_customMetadata_creator", + applicationProperties.getEnterpriseEdition().getCustomMetadata().getCreator()); + addIfNotEmpty( + properties, + "enterpriseEdition_customMetadata_producer", + applicationProperties.getEnterpriseEdition().getCustomMetadata().getProducer()); + } + // Capture AutoPipeline properties + addIfNotEmpty( + properties, + "autoPipeline_outputFolder", + applicationProperties.getAutoPipeline().getOutputFolder()); + + return properties; + } + + private String getMacAddress() { + try { + Enumeration networkInterfaces = + NetworkInterface.getNetworkInterfaces(); + while (networkInterfaces.hasMoreElements()) { + NetworkInterface ni = networkInterfaces.nextElement(); + byte[] hardwareAddress = ni.getHardwareAddress(); + if (hardwareAddress != null) { + String[] hexadecimal = new String[hardwareAddress.length]; + for (int i = 0; i < hardwareAddress.length; i++) { + hexadecimal[i] = String.format("%02X", hardwareAddress[i]); + } + return String.join("-", hexadecimal); + } + } + } catch (Exception e) { + // Handle exception + } + return "Unknown"; + } + + private Map getNetworkInterfacesInfo() { + Map interfacesInfo = new HashMap<>(); + try { + Enumeration nets = NetworkInterface.getNetworkInterfaces(); + while (nets.hasMoreElements()) { + NetworkInterface netint = nets.nextElement(); + interfacesInfo.put(netint.getName(), netint.getDisplayName()); + } + } catch (Exception e) { + interfacesInfo.put("error", e.getMessage()); + } + return interfacesInfo; + } +} diff --git a/src/main/java/stirling/software/SPDF/utils/CustomHtmlSanitizer.java b/src/main/java/stirling/software/SPDF/utils/CustomHtmlSanitizer.java new file mode 100644 index 00000000000..587aae6fa6b --- /dev/null +++ b/src/main/java/stirling/software/SPDF/utils/CustomHtmlSanitizer.java @@ -0,0 +1,21 @@ +package stirling.software.SPDF.utils; + +import org.owasp.html.HtmlPolicyBuilder; +import org.owasp.html.PolicyFactory; +import org.owasp.html.Sanitizers; + +public class CustomHtmlSanitizer { + private static final PolicyFactory POLICY = + Sanitizers.FORMATTING + .and(Sanitizers.BLOCKS) + .and(Sanitizers.STYLES) + .and(Sanitizers.LINKS) + .and(Sanitizers.TABLES) + .and(Sanitizers.IMAGES) + .and(new HtmlPolicyBuilder().disallowElements("noscript").toFactory()); + + public static String sanitize(String html) { + String htmlAfter = POLICY.sanitize(html); + return htmlAfter; + } +} diff --git a/src/main/java/stirling/software/SPDF/utils/FileToPdf.java b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java index c712342445f..d18277df29a 100644 --- a/src/main/java/stirling/software/SPDF/utils/FileToPdf.java +++ b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java @@ -2,16 +2,23 @@ import java.io.ByteArrayInputStream; import java.io.File; +import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; import io.github.pixee.security.ZipSecurity; @@ -33,19 +40,25 @@ public static byte[] convertHtmlToPdf( try { if (fileName.endsWith(".html")) { tempInputFile = Files.createTempFile("input_", ".html"); - Files.write(tempInputFile, fileBytes); - } else { + String sanitizedHtml = + sanitizeHtmlContent(new String(fileBytes, StandardCharsets.UTF_8)); + Files.write(tempInputFile, sanitizedHtml.getBytes(StandardCharsets.UTF_8)); + } else if (fileName.endsWith(".zip")) { tempInputFile = Files.createTempFile("input_", ".zip"); Files.write(tempInputFile, fileBytes); + sanitizeHtmlFilesInZip(tempInputFile); + } else { + throw new IllegalArgumentException("Unsupported file format: " + fileName); } List command = new ArrayList<>(); if (!htmlFormatsInstalled) { command.add("weasyprint"); - command.add("-e utf-8"); + command.add("-e"); + command.add("utf-8"); + command.add("-v"); command.add(tempInputFile.toString()); command.add(tempOutputFile.toString()); - } else { command.add("ebook-convert"); command.add(tempInputFile.toString()); @@ -54,10 +67,8 @@ public static byte[] convertHtmlToPdf( command.add("a4"); if (request != null && request.getZoom() != 1.0) { - // Create a temporary CSS file File tempCssFile = Files.createTempFile("customStyle", ".css").toFile(); try (FileWriter writer = new FileWriter(tempCssFile)) { - // Write the CSS rule to the file writer.write("body { zoom: " + request.getZoom() + "; }"); } command.add("--extra-css"); @@ -65,9 +76,7 @@ public static byte[] convertHtmlToPdf( } } - ProcessExecutorResult returnCode; - - returnCode = + ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) .runCommandWithOutputHandling(command); @@ -78,8 +87,6 @@ public static byte[] convertHtmlToPdf( throw e; } } finally { - - // Clean up temporary files Files.deleteIfExists(tempOutputFile); Files.deleteIfExists(tempInputFile); } @@ -87,6 +94,81 @@ public static byte[] convertHtmlToPdf( return pdfBytes; } + private static String sanitizeHtmlContent(String htmlContent) { + return CustomHtmlSanitizer.sanitize(htmlContent); + } + + private static void sanitizeHtmlFilesInZip(Path zipFilePath) throws IOException { + Path tempUnzippedDir = Files.createTempDirectory("unzipped_"); + try (ZipInputStream zipIn = + ZipSecurity.createHardenedInputStream( + new ByteArrayInputStream(Files.readAllBytes(zipFilePath)))) { + ZipEntry entry = zipIn.getNextEntry(); + while (entry != null) { + Path filePath = tempUnzippedDir.resolve(entry.getName()); + if (!entry.isDirectory()) { + Files.createDirectories(filePath.getParent()); + if (entry.getName().toLowerCase().endsWith(".html") + || entry.getName().toLowerCase().endsWith(".htm")) { + String content = new String(zipIn.readAllBytes(), StandardCharsets.UTF_8); + String sanitizedContent = sanitizeHtmlContent(content); + Files.write(filePath, sanitizedContent.getBytes(StandardCharsets.UTF_8)); + } else { + Files.copy(zipIn, filePath); + } + } + zipIn.closeEntry(); + entry = zipIn.getNextEntry(); + } + } + + // Repack the sanitized files + zipDirectory(tempUnzippedDir, zipFilePath); + + // Clean up + deleteDirectory(tempUnzippedDir); + } + + private static void zipDirectory(Path sourceDir, Path zipFilePath) throws IOException { + try (ZipOutputStream zos = + new ZipOutputStream(new FileOutputStream(zipFilePath.toFile()))) { + Files.walk(sourceDir) + .filter(path -> !Files.isDirectory(path)) + .forEach( + path -> { + ZipEntry zipEntry = + new ZipEntry(sourceDir.relativize(path).toString()); + try { + zos.putNextEntry(zipEntry); + Files.copy(path, zos); + zos.closeEntry(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + } + + private static void deleteDirectory(Path dir) throws IOException { + Files.walkFileTree( + dir, + new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) + throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + } + private static Path unzipAndGetMainHtml(byte[] fileBytes) throws IOException { Path tempDirectory = Files.createTempDirectory("unzipped_"); try (ZipInputStream zipIn = diff --git a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java index 21d921c833e..8e56c8df6e8 100644 --- a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java @@ -5,18 +5,28 @@ import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; +import java.net.InetAddress; import java.net.MalformedURLException; +import java.net.NetworkInterface; import java.net.URI; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.security.MessageDigest; import java.util.ArrayList; +import java.util.Enumeration; import java.util.List; +import java.util.UUID; +import org.simpleyaml.configuration.file.YamlFile; +import org.simpleyaml.configuration.file.YamlFileWrapper; +import org.simpleyaml.configuration.implementation.SimpleYamlImplementation; +import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.multipart.MultipartFile; @@ -262,4 +272,81 @@ public static boolean createDir(String path) { } return true; } + + public static boolean isValidUUID(String uuid) { + if (uuid == null) { + return false; + } + try { + UUID.fromString(uuid); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + public static void saveKeyToConfig(String id, String key) throws IOException { + saveKeyToConfig(id, key, true); + } + + public static void saveKeyToConfig(String id, String key, boolean autoGenerated) + throws IOException { + Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml + + final YamlFile settingsYml = new YamlFile(path.toFile()); + DumperOptions yamlOptionssettingsYml = + ((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions(); + yamlOptionssettingsYml.setSplitLines(false); + + settingsYml.loadWithComments(); + + YamlFileWrapper writer = settingsYml.path(id).set(key); + if (autoGenerated) { + writer.comment("# Automatically Generated Settings (Do Not Edit Directly)"); + } + settingsYml.save(); + } + + public static String generateMachineFingerprint() { + try { + // Get the MAC address + StringBuilder sb = new StringBuilder(); + InetAddress ip = InetAddress.getLocalHost(); + NetworkInterface network = NetworkInterface.getByInetAddress(ip); + + if (network == null) { + Enumeration networks = NetworkInterface.getNetworkInterfaces(); + while (networks.hasMoreElements()) { + NetworkInterface net = networks.nextElement(); + byte[] mac = net.getHardwareAddress(); + if (mac != null) { + for (int i = 0; i < mac.length; i++) { + sb.append(String.format("%02X", mac[i])); + } + break; // Use the first network interface with a MAC address + } + } + } else { + byte[] mac = network.getHardwareAddress(); + if (mac != null) { + for (int i = 0; i < mac.length; i++) { + sb.append(String.format("%02X", mac[i])); + } + } + } + + // Hash the MAC address for privacy and consistency + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] hash = md.digest(sb.toString().getBytes(StandardCharsets.UTF_8)); + StringBuilder fingerprint = new StringBuilder(); + for (byte b : hash) { + fingerprint.append(String.format("%02x", b)); + } + + return fingerprint.toString(); + + } catch (Exception e) { + return "GenericID"; + } + } } diff --git a/src/main/java/stirling/software/SPDF/utils/PDFToFile.java b/src/main/java/stirling/software/SPDF/utils/PDFToFile.java index 920e987b8d1..1a1957cf715 100644 --- a/src/main/java/stirling/software/SPDF/utils/PDFToFile.java +++ b/src/main/java/stirling/software/SPDF/utils/PDFToFile.java @@ -191,7 +191,6 @@ public ResponseEntity processPdfToOfficeFormat( Files.deleteIfExists(tempInputFile); if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile()); } - System.out.println("fileBytes=" + fileBytes.length); return WebResponseUtils.bytesToWebResponse( fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM); } diff --git a/src/main/java/stirling/software/SPDF/utils/RequestUriUtils.java b/src/main/java/stirling/software/SPDF/utils/RequestUriUtils.java index 7aeab8e177f..7033fa7a706 100644 --- a/src/main/java/stirling/software/SPDF/utils/RequestUriUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/RequestUriUtils.java @@ -17,6 +17,7 @@ public static boolean isStaticResource(String contextPath, String requestURI) { || requestURI.startsWith(contextPath + "/public/") || requestURI.startsWith(contextPath + "/pdfjs/") || requestURI.startsWith(contextPath + "/login") + || requestURI.startsWith(contextPath + "/error") || requestURI.endsWith(".svg") || requestURI.endsWith(".png") || requestURI.endsWith(".ico") diff --git a/src/main/java/stirling/software/SPDF/utils/misc/ReplaceAndInvertColorStrategy.java b/src/main/java/stirling/software/SPDF/utils/misc/ReplaceAndInvertColorStrategy.java index 87590731465..024e0e706f8 100644 --- a/src/main/java/stirling/software/SPDF/utils/misc/ReplaceAndInvertColorStrategy.java +++ b/src/main/java/stirling/software/SPDF/utils/misc/ReplaceAndInvertColorStrategy.java @@ -6,11 +6,12 @@ import org.springframework.web.multipart.MultipartFile; import lombok.Data; +import lombok.EqualsAndHashCode; import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.model.api.misc.ReplaceAndInvert; @Data -// @EqualsAndHashCode(callSuper = true) +@EqualsAndHashCode(callSuper = true) public abstract class ReplaceAndInvertColorStrategy extends PDFFile { protected ReplaceAndInvert replaceAndInvert; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index cae1dce3d4a..0f957c0a997 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -27,9 +27,9 @@ server.servlet.context-path=${SYSTEM_ROOTURIPATH:/} spring.devtools.restart.enabled=true spring.devtools.livereload.enabled=true - spring.thymeleaf.encoding=UTF-8 +spring.web.resources.mime-mappings.webmanifest=application/manifest+json spring.mvc.async.request-timeout=${SYSTEM_CONNECTIONTIMEOUTMILLISECONDS:1200000} #spring.thymeleaf.prefix=file:/customFiles/templates/,classpath:/templates/ @@ -41,7 +41,7 @@ spring.datasource.username=sa spring.datasource.password= spring.h2.console.enabled=false spring.jpa.hibernate.ddl-auto=update - +server.servlet.session.timeout: 30m # Change the default URL path for OpenAPI JSON springdoc.api-docs.path=/v1/api-docs @@ -49,3 +49,5 @@ springdoc.api-docs.path=/v1/api-docs springdoc.swagger-ui.url=/v1/api-docs +posthog.api.key=phc_fiR65u5j6qmXTYL56MNrLZSWqLaDW74OrZH0Insd2xq +posthog.host=https://eu.i.posthog.com \ No newline at end of file diff --git a/src/main/resources/messages_ar_AR.properties b/src/main/resources/messages_ar_AR.properties index 19fe9944553..0c2f6188669 100644 --- a/src/main/resources/messages_ar_AR.properties +++ b/src/main/resources/messages_ar_AR.properties @@ -76,6 +76,7 @@ donate=تبرع color=لون sponsor=راعٍ info=معلومات +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=خط الأنابيب: pipelineOptions.saveButton=تنزيل pipelineOptions.validateButton=تحقق - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=تحويل من PDF navbar.sections.security=التوقيع والأمان navbar.sections.advance=متقدم navbar.sections.edit=عرض وتعديل +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=لم يتم العثور على الملف database.fileNullOrEmpty=يجب ألا يكون الملف فارغًا أو خاليًا database.failedImportFile=فشل استيراد الملف +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -382,7 +402,7 @@ home.scalePages.title=ضبط حجم/مقياس الصفحة home.scalePages.desc=تغيير حجم/مقياس الصفحة و/أو محتواها. scalePages.tags=تغيير الحجم,تعديل,الأبعاد,تكييف -home.pipeline.title=خط الأنابيب (متقدم) +home.pipeline.title=خط الأنابيب home.pipeline.desc=تشغيل إجراءات متعددة على ملفات PDF عن طريق تحديد نصوص خط الأنابيب pipeline.tags=أتمتة,تسلسل,مبرمج,معالجة دفعات @@ -482,6 +502,11 @@ home.removeImagePdf.title=إزالة الصورة home.removeImagePdf.desc=إزالة الصورة من PDF لتقليل حجم الملف removeImagePdf.tags=إزالة الصورة,عمليات الصفحة,الخلفية,جانب الخادم + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=تم رفض الوصول login.oauth2InvalidTokenResponse=استجابة الرمز المميز غير صالحة login.oauth2InvalidIdToken=رمز الهوية غير صالح login.userIsDisabled=تم تعطيل المستخدم، تم حظر تسجيل الدخول حاليًا باستخدام اسم المستخدم هذا. يرجى الاتصال بالمسؤول. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=حجب تلقائي @@ -1154,6 +1182,8 @@ licenses.license=الترخيص survey.nav=استطلاع survey.title=استطلاع Stirling-PDF survey.description=Stirling-PDF لا يحتوي على تتبع لذا نريد أن نسمع من مستخدمينا لتحسين Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=يرجى النظر في المشاركة في استطلاعنا! survey.disabled=(سيتم تعطيل النافذة المنبثقة للاستطلاع في التحديثات التالية ولكنها ستكون متاحة في أسفل الصفحة) survey.button=المشاركة في الاستطلاع @@ -1179,3 +1209,17 @@ removeImage.title=إزالة الصورة removeImage.header=إزالة الصورة removeImage.removeImage=إزالة الصورة removeImage.submit=إزالة الصورة + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_bg_BG.properties b/src/main/resources/messages_bg_BG.properties index 2e224ba7494..54db6e891a3 100644 --- a/src/main/resources/messages_bg_BG.properties +++ b/src/main/resources/messages_bg_BG.properties @@ -76,6 +76,7 @@ donate=Направете дарение color=Цвят sponsor=Спонсор info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Изтегли pipelineOptions.validateButton=Валидирай - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Преобразуване от PDF navbar.sections.security=Подписване и сигурност navbar.sections.advance=Разширено navbar.sections.edit=Преглед и редактиране +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Отказан достъп login.oauth2InvalidTokenResponse=Невалиден отговор на токена login.oauth2InvalidIdToken=Невалиден токен за идентификатор login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Автоматично редактиране @@ -1154,6 +1182,8 @@ licenses.license=Лиценз survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Please consider taking our survey! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_ca_CA.properties b/src/main/resources/messages_ca_CA.properties index 48ffcb693cb..7c4967f7b47 100644 --- a/src/main/resources/messages_ca_CA.properties +++ b/src/main/resources/messages_ca_CA.properties @@ -76,6 +76,7 @@ donate=Donate color=Color sponsor=Sponsor info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Download pipelineOptions.validateButton=Validate - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF navbar.sections.security=Sign & Security navbar.sections.advance=Advanced navbar.sections.edit=View & Edit +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -382,7 +402,7 @@ home.scalePages.title=Adjust page size/scale home.scalePages.desc=Change the size/scale of page and/or its contents. scalePages.tags=resize,modify,dimension,adapt -home.pipeline.title=Pipeline (Advanced) +home.pipeline.title=Pipeline home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts pipeline.tags=automate,sequence,scripted,batch-process @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Access Denied login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidIdToken=Invalid Id Token login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Auto Redact @@ -1154,6 +1182,8 @@ licenses.license=License survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Please consider taking our survey! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_cs_CZ.properties b/src/main/resources/messages_cs_CZ.properties index 4f4ec782132..6798197adb2 100644 --- a/src/main/resources/messages_cs_CZ.properties +++ b/src/main/resources/messages_cs_CZ.properties @@ -76,6 +76,7 @@ donate=Přispějte color=Barva sponsor=Sponzor info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Stáhnout pipelineOptions.validateButton=Ověřit - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Převést z PDF navbar.sections.security=Podpis a Bezpečnost navbar.sections.advance=Pokročilé navbar.sections.edit=Prohlédnout a Upravit +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -382,7 +402,7 @@ home.scalePages.title=Upravit velikost/škálu stránky home.scalePages.desc=Změnit velikost/škálu stránky a/nebo její obsah. scalePages.tags=změnit velikost,upravit,rozměr,přizpůsobit -home.pipeline.title=Potrubí (Pokročilé) +home.pipeline.title=Potrubí home.pipeline.desc=Spustit více akcí na PDF s definicí skriptů potrubí pipeline.tags=automatizovat,sekvence,skriptované,dávkové zpracování @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Access Denied login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidIdToken=Invalid Id Token login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Auto Redact @@ -1154,6 +1182,8 @@ licenses.license=Licence survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Please consider taking our survey! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_da_DK.properties b/src/main/resources/messages_da_DK.properties index 7e2c4538d9e..667d843476c 100644 --- a/src/main/resources/messages_da_DK.properties +++ b/src/main/resources/messages_da_DK.properties @@ -76,6 +76,7 @@ donate=Donér color=Farve sponsor=Sponsor info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Download pipelineOptions.validateButton=Validér - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Konvertér fra PDF navbar.sections.security=Signér & Sikkerhed navbar.sections.advance=Avanceret navbar.sections.edit=Vis & Redigér +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=Fil ikke fundet database.fileNullOrEmpty=Fil må ikke være null eller tom database.failedImportFile=Kunne ikke importere fil +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Fjern billede home.removeImagePdf.desc=Fjern billede fra PDF for at reducere filstørrelse removeImagePdf.tags=Fjern Billede,Sideoperationer,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Adgang Nægtet login.oauth2InvalidTokenResponse=Ugyldigt Token Svar login.oauth2InvalidIdToken=Ugyldigt Id Token login.userIsDisabled=Bruger er deaktiveret, login er i øjeblikket blokeret med dette brugernavn. Kontakt venligst administratoren. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Auto Rediger @@ -1154,6 +1182,8 @@ licenses.license=License survey.nav=Undersøgelse survey.title=Stirling-PDF Undersøgelse survey.description=Stirling-PDF har ingen sporing, så vi vil gerne høre fra vores brugere for at forbedre Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Overvej venligst at deltage i vores undersøgelse! survey.disabled=(Undersøgelsespop-up vil blive deaktiveret i følgende opdateringer, men vil være tilgængelig i bunden af siden) survey.button=Tag Undersøgelsen @@ -1179,3 +1209,17 @@ removeImage.title=Fjern billede removeImage.header=Fjern billede removeImage.removeImage=Fjern billede removeImage.submit=Fjern + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_de_DE.properties b/src/main/resources/messages_de_DE.properties index e156dbf2a0b..ddaa0dbdfb7 100644 --- a/src/main/resources/messages_de_DE.properties +++ b/src/main/resources/messages_de_DE.properties @@ -76,6 +76,7 @@ donate=Spenden color=Farbe sponsor=Sponsor info=Informationen +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Herunterladen pipelineOptions.validateButton=Validieren - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Konvertieren von PDF navbar.sections.security=Zeichen und Sicherheit navbar.sections.advance=Fortschrittlich navbar.sections.edit=Anzeigen und Bearbeiten +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=Datei nicht gefunden database.fileNullOrEmpty=Datei darf nicht null oder leer sein database.failedImportFile=Dateiimport fehlgeschlagen +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -382,7 +402,7 @@ home.scalePages.title=Seitengröße/Skalierung anpassen home.scalePages.desc=Größe/Skalierung der Seite und/oder des Inhalts ändern scalePages.tags=größe ändern,ändern,dimensionieren,anpassen -home.pipeline.title=Pipeline (Fortgeschritten) +home.pipeline.title=Pipeline home.pipeline.desc=Mehrere Aktionen auf ein PDF anwenden, definiert durch ein Pipeline Skript pipeline.tags=automatisieren,sequenzieren,skriptgesteuert,batch prozess @@ -482,6 +502,11 @@ home.removeImagePdf.title=Bild entfernen home.removeImagePdf.desc=Bild aus PDF entfernen, um die Dateigröße zu verringern removeImagePdf.tags=bild entfernen,seitenoperationen,back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Zugriff abgelehnt login.oauth2InvalidTokenResponse=Ungültige Token-Antwort login.oauth2InvalidIdToken=Ungültiges ID-Token login.userIsDisabled=Benutzer ist deaktiviert, die Anmeldung ist mit diesem Benutzernamen derzeit gesperrt. Bitte wenden Sie sich an den Administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Automatisch zensieren/schwärzen @@ -1154,6 +1182,8 @@ licenses.license=Lizenz survey.nav=Umfrage survey.title=Stirling-PDF-Umfrage survey.description=Stirling-PDF hat kein Tracking, daher möchten wir von unseren Benutzern hören, wie wir Stirling-PDF verbessern können! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Bitte nehmen Sie an unserer Umfrage teil! survey.disabled=(Das Umfrage-Popup wird in folgenden Updates deaktiviert, ist aber am Fuß der Seite verfügbar.) survey.button=Umfrage durchführen @@ -1179,3 +1209,17 @@ removeImage.title=Bild entfernen removeImage.header=Bild entfernen removeImage.removeImage=Bild entfernen removeImage.submit=Bild entfernen + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_el_GR.properties b/src/main/resources/messages_el_GR.properties index b19770e0e9b..3154db02f3a 100644 --- a/src/main/resources/messages_el_GR.properties +++ b/src/main/resources/messages_el_GR.properties @@ -76,6 +76,7 @@ donate=Δωρισε color=Χρώμα sponsor=Yποστηρικτής info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Λήψη pipelineOptions.validateButton=Επικυρώνω - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF navbar.sections.security=Sign & Security navbar.sections.advance=Advanced navbar.sections.edit=View & Edit +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Access Denied login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidIdToken=Invalid Id Token login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Αυτόματο Μαύρισμα Κειμένου @@ -1154,6 +1182,8 @@ licenses.license=Άδεια survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Please consider taking our survey! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index dbebccc0154..6cdfaadbbfb 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -76,6 +76,7 @@ donate=Donate color=Color sponsor=Sponsor info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Download pipelineOptions.validateButton=Validate - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF navbar.sections.security=Sign & Security navbar.sections.advance=Advanced navbar.sections.edit=View & Edit +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed to import file +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -382,7 +402,7 @@ home.scalePages.title=Adjust page size/scale home.scalePages.desc=Change the size/scale of a page and/or its contents. scalePages.tags=resize,modify,dimension,adapt -home.pipeline.title=Pipeline (Advanced) +home.pipeline.title=Pipeline home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts pipeline.tags=automate,sequence,scripted,batch-process @@ -482,10 +502,15 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color -replace-color.title=Replace-Invert-Color +replace-color.title=Advanced Colour options replace-color.header=Replace-Invert Color PDF -home.replaceColorPdf.title=Replace and Invert Color +home.replaceColorPdf.title=Advanced Colour options home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size replaceColorPdf.tags=Replace Color,Page operations,Back end,server side replace-color.selectText.1=Replace or Invert color Options @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Access Denied login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidIdToken=Invalid Id Token login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Auto Redact @@ -1154,7 +1182,9 @@ licenses.license=Licence survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! -survey.please=Please consider taking our survey! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding +survey.please=Please consider taking our survey to have input on the future of Stirling-PDF! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey survey.dontShowAgain=Don't show again @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_en_US.properties b/src/main/resources/messages_en_US.properties index 6ea5dd95996..41bd756aba7 100644 --- a/src/main/resources/messages_en_US.properties +++ b/src/main/resources/messages_en_US.properties @@ -76,6 +76,7 @@ donate=Donate color=Color sponsor=Sponsor info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Download pipelineOptions.validateButton=Validate - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF navbar.sections.security=Sign & Security navbar.sections.advance=Advanced navbar.sections.edit=View & Edit +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Access Denied login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidIdToken=Invalid Id Token login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Auto Redact @@ -1154,6 +1182,8 @@ licenses.license=License survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Please consider taking our survey! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_es_ES.properties b/src/main/resources/messages_es_ES.properties index 5274fc1d2e2..0f45980e171 100644 --- a/src/main/resources/messages_es_ES.properties +++ b/src/main/resources/messages_es_ES.properties @@ -76,6 +76,7 @@ donate=Donar color=Color sponsor=Patrocinador info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Canalización: pipelineOptions.saveButton=Descargar pipelineOptions.validateButton=Validar - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Convertir desde PDF navbar.sections.security=Señalización y seguridad navbar.sections.advance=Avanzado navbar.sections.edit=Ver y Editar +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=Archivo no encontrado database.fileNullOrEmpty=El archivo no debe ser nulo o vacío. database.failedImportFile=Archivo de importación fallido +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -382,7 +402,7 @@ home.scalePages.title=Escalar/ajustar tamaño de página home.scalePages.desc=Escalar/cambiar el tamaño de una pagina y/o su contenido scalePages.tags=cambiar tamaño,modificar,dimensionar,adaptar -home.pipeline.title=Secuencia (Avanzado) +home.pipeline.title=Secuencia home.pipeline.desc=Ejecutar varias tareas a PDFs definiendo una secuencia de comandos pipeline.tags=automatizar,secuencia,con script,proceso por lotes @@ -482,6 +502,11 @@ home.removeImagePdf.title=Eliminar imagen home.removeImagePdf.desc=Eliminar imagen del PDF> para reducir el tamaño de archivo removeImagePdf.tags=Eliminar imagen,Operaciones de página,Back end,lado del servidor + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Acceso denegado login.oauth2InvalidTokenResponse=Respuesta de token no válida login.oauth2InvalidIdToken=Token de identificación no válido login.userIsDisabled=El usuario está desactivado, actualmente el acceso está bloqueado para ese nombre de usuario. Por favor, póngase en contacto con el administrador. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Auto Redactar @@ -1154,6 +1182,8 @@ licenses.license=Licencia survey.nav=Encuesta survey.title=Encuesta Stirling-PDF survey.description=Stirling-PDF no tiene seguimiento, por lo que queremos escuchar a nuestros usuarios para mejorar Stirling-PDF. +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=¡Considere realizar nuestra encuesta! survey.disabled=(La ventana emergente de la encuesta se desactivará en las siguientes actualizaciones, pero estará disponible al pie de la página.) survey.button=Realizar encuesta @@ -1179,3 +1209,17 @@ removeImage.title=Eliminar imagen removeImage.header=Eliminar imagen removeImage.removeImage=Eliminar imagen removeImage.submit=Eliminar imagen + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_eu_ES.properties b/src/main/resources/messages_eu_ES.properties index 792955c3262..7c56fed98dd 100644 --- a/src/main/resources/messages_eu_ES.properties +++ b/src/main/resources/messages_eu_ES.properties @@ -76,6 +76,7 @@ donate=Donate color=Color sponsor=Sponsor info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Download pipelineOptions.validateButton=Validate - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF navbar.sections.security=Sign & Security navbar.sections.advance=Advanced navbar.sections.edit=View & Edit +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Access Denied login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidIdToken=Invalid Id Token login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Auto Idatzi @@ -1154,6 +1182,8 @@ licenses.license=License survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Please consider taking our survey! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_fr_FR.properties b/src/main/resources/messages_fr_FR.properties index ee00bb486ad..b6756e8a008 100644 --- a/src/main/resources/messages_fr_FR.properties +++ b/src/main/resources/messages_fr_FR.properties @@ -76,6 +76,7 @@ donate=Faire un don color=Couleur sponsor=Sponsor info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Télécharger pipelineOptions.validateButton=Valider - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Convertir depuis PDF navbar.sections.security=Signature et sécurité navbar.sections.advance=Mode avancé navbar.sections.edit=Voir et modifier +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -382,7 +402,7 @@ home.scalePages.title=Ajuster l’échelle ou la taille home.scalePages.desc=Modifiez la taille ou l’échelle d’une page et/ou de son contenu. scalePages.tags=ajuster,redimensionner,resize,modify,dimension,adapt -home.pipeline.title=Pipeline (avancé) +home.pipeline.title=Pipeline home.pipeline.desc=Exécutez plusieurs actions sur les PDF en définissant des scripts de pipeline. pipeline.tags=automatiser,séquencer,automate,sequence,scripted,batch-process @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Accès refusé login.oauth2InvalidTokenResponse=Réponse contenant le jeton est invalide login.oauth2InvalidIdToken=Jeton d'identification invalide login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Caviarder automatiquement @@ -1154,6 +1182,8 @@ licenses.license=Licence survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Please consider taking our survey! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_ga_IE.properties b/src/main/resources/messages_ga_IE.properties index 2360538a952..a8970c7557c 100644 --- a/src/main/resources/messages_ga_IE.properties +++ b/src/main/resources/messages_ga_IE.properties @@ -76,6 +76,7 @@ donate=Síntiúis color=Dath sponsor=Urraitheoir info=Eolas +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Píblíne: pipelineOptions.saveButton=Íosluchtaigh pipelineOptions.validateButton=Bailíochtaigh - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Tiontaigh ó PDF navbar.sections.security=Comhartha & Slándáil navbar.sections.advance=Casta navbar.sections.edit=Féach ar & Cuir in Eagar +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=Comhad gan aimsiú database.fileNullOrEmpty=Níor cheart go mbeadh an comhad ar neamhní nó folamh database.failedImportFile=Theip ar iompórtáil an chomhaid +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Rochtain Diúltaithe login.oauth2InvalidTokenResponse=Freagra Comhartha Neamhbhailí login.oauth2InvalidIdToken=Comhartha Aitheantais Neamhbhailí login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Auto Redact @@ -1154,6 +1182,8 @@ licenses.license=Ceadúnas survey.nav=Suirbhé survey.title=Suirbhé Stirling-PDF survey.description=Níl aon rian ar Stirling-PDF agus mar sin ba mhaith linn cloisteáil ónár n-úsáideoirí chun feabhas a chur ar Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Smaoinigh ar ár suirbhé a dhéanamh le do thoil! survey.disabled=(Díchumasófar aníos an tsuirbhé sna nuashonruithe seo a leanas ach beidh siad ar fáil ag bun an leathanaigh) survey.button=Tóg Suirbhé @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_hi_IN.properties b/src/main/resources/messages_hi_IN.properties index d93dc2ddec1..58d2257f895 100644 --- a/src/main/resources/messages_hi_IN.properties +++ b/src/main/resources/messages_hi_IN.properties @@ -76,6 +76,7 @@ donate=Donate color=Color sponsor=Sponsor info=Info +pro=Pro page=पृष्ठ pages=पृष्ठों @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Download pipelineOptions.validateButton=Validate - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=पीडीएफ से कनवर्ट कर navbar.sections.security=संकेत और सुरक्षा navbar.sections.advance=उन्नत navbar.sections.edit=देखें और संपादित करें +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Access Denied login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidIdToken=Invalid Id Token login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=स्वत: गोपनीयकरण @@ -1154,6 +1182,8 @@ licenses.license=License survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Please consider taking our survey! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_hr_HR.properties b/src/main/resources/messages_hr_HR.properties index e8f93b20015..b31ad75b9b5 100644 --- a/src/main/resources/messages_hr_HR.properties +++ b/src/main/resources/messages_hr_HR.properties @@ -76,6 +76,7 @@ donate=Doniraj color=Boja sponsor=Sponzor info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Preuzmi datoteku pipelineOptions.validateButton=Potvrdi - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Pretvori iz PDF navbar.sections.security=Potpis & sigurnost navbar.sections.advance=Napredno navbar.sections.edit=Pregled & Uređivanje +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -382,7 +402,7 @@ home.scalePages.title=Prilagodite veličinu/razmjer stranice home.scalePages.desc=Promijenite veličinu/razmjer stranice i/ili njezin sadržaj. scalePages.tags=izmjena,modifikacija,dimenzija,adaptacija -home.pipeline.title=Pipeline (Advanced) +home.pipeline.title=Pipeline home.pipeline.desc=Izvršite više radnji na PDF-ovima definiranjem skripti u pipeline-u pipeline.tags=automatizacija,sekvenciranje,skriptirano,batch-process @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Pristup odbijen login.oauth2InvalidTokenResponse=Nevažeći odgovor tokena login.oauth2InvalidIdToken=Nevažeći ID token login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Automatsko uređivanje @@ -1154,6 +1182,8 @@ licenses.license=Licenca survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Please consider taking our survey! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_hu_HU.properties b/src/main/resources/messages_hu_HU.properties index e22cee08c4d..e5457609377 100644 --- a/src/main/resources/messages_hu_HU.properties +++ b/src/main/resources/messages_hu_HU.properties @@ -76,6 +76,7 @@ donate=Donate color=Color sponsor=Sponsor info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Download pipelineOptions.validateButton=Validate - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF navbar.sections.security=Sign & Security navbar.sections.advance=Advanced navbar.sections.edit=View & Edit +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Access Denied login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidIdToken=Invalid Id Token login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Érzékeny tartalom eltávolítása @@ -1154,6 +1182,8 @@ licenses.license=License survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Please consider taking our survey! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_id_ID.properties b/src/main/resources/messages_id_ID.properties index cbc0e61a196..9c4f8bc3f24 100644 --- a/src/main/resources/messages_id_ID.properties +++ b/src/main/resources/messages_id_ID.properties @@ -76,6 +76,7 @@ donate=Donate color=Color sponsor=Sponsor info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Download pipelineOptions.validateButton=Validate - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF navbar.sections.security=Sign & Security navbar.sections.advance=Advanced navbar.sections.edit=View & Edit +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -382,7 +402,7 @@ home.scalePages.title=Menyesuaikan ukuran/skala halaman home.scalePages.desc=Mengubah ukuran/skala halaman dan/atau isinya. scalePages.tags=mengubah ukuran, memodifikasi, dimensi, mengadaptasi -home.pipeline.title=Pipeline (Lanjutan) +home.pipeline.title=Pipeline home.pipeline.desc=Menjalankan beberapa tindakan pada PDF dengan mendefinisikan skrip pipeline pipeline.tags=mengotomatiskan, mengurutkan, menulis, proses batch @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Access Denied login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidIdToken=Invalid Id Token login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Redaksional Otomatis @@ -1154,6 +1182,8 @@ licenses.license=License survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Please consider taking our survey! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_it_IT.properties b/src/main/resources/messages_it_IT.properties index e8fb9fcca38..8ba53f6d180 100644 --- a/src/main/resources/messages_it_IT.properties +++ b/src/main/resources/messages_it_IT.properties @@ -76,6 +76,7 @@ donate=Donazione color=Colore sponsor=Sponsor info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Download pipelineOptions.validateButton=Convalidare - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Converti da PDF navbar.sections.security=Firma & Sicurezza navbar.sections.advance=Avanzate navbar.sections.edit=Visualizza & Modifica +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File non trovato database.fileNullOrEmpty=Il file non deve essere nullo o vuoto database.failedImportFile=Importazione file non riuscita +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -382,7 +402,7 @@ home.scalePages.title=Regola le dimensioni/scala della pagina home.scalePages.desc=Modificare le dimensioni/scala della pagina e/o dei suoi contenuti. scalePages.tags=ridimensionare,modificare,dimensionare,adattare -home.pipeline.title=Pipeline (avanzato) +home.pipeline.title=Pipeline home.pipeline.desc=Esegui più azioni sui PDF definendo script di pipeline pipeline.tags=automatizzare,sequenziare,scriptare,elaborare in batch @@ -482,6 +502,11 @@ home.removeImagePdf.title=Rimuovi immagine home.removeImagePdf.desc=Rimuovi le immagini dal PDF per ridurre la dimensione del file removeImagePdf.tags=Rimuovi immagine,operazioni sulla pagina,back-end,lato server + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Sostituisci-Inverti-Colore replace-color.header=Sostituisci-Inverti colore PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Accesso negato login.oauth2InvalidTokenResponse=Risposta token non valida login.oauth2InvalidIdToken=Id Token non valido login.userIsDisabled=L'utente è disattivato, l'accesso è attualmente bloccato con questo nome utente. Si prega di contattare l'amministratore. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Redazione automatica @@ -1154,6 +1182,8 @@ licenses.license=Licenza survey.nav=Sondaggio survey.title=Sondaggio Stirling-PDF survey.description=Stirling-PDF non fa tracciamento, quindi vogliamo sentire i nostri utenti per migliorare Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Ti invitiamo a prendere in considerazione la possibilità di partecipare al nostro sondaggio! survey.disabled=(Il popup del sondaggio verrà disabilitato nei prossimi aggiornamenti ma sarà disponibile a piè di pagina) survey.button=Partecipa al sondaggio @@ -1179,3 +1209,17 @@ removeImage.title=Rimuovere immagine removeImage.header=Rimuovi immagine removeImage.removeImage=Rimuovi immagine removeImage.submit=Rimuovi immagine + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_ja_JP.properties b/src/main/resources/messages_ja_JP.properties index e5286295d3c..473bcb10d0a 100644 --- a/src/main/resources/messages_ja_JP.properties +++ b/src/main/resources/messages_ja_JP.properties @@ -76,6 +76,7 @@ donate=寄付する color=色 sponsor=スポンサー info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=パイプライン: pipelineOptions.saveButton=ダウンロード pipelineOptions.validateButton=検証 - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=PDFから変換 navbar.sections.security=署名とセキュリティ navbar.sections.advance=アドバンスド navbar.sections.edit=閲覧と編集 +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=ファイルが見つかりません database.fileNullOrEmpty=ファイルは null または空であってはなりません database.failedImportFile=ファイルのインポートに失敗 +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -382,7 +402,7 @@ home.scalePages.title=ページの縮尺の調整 home.scalePages.desc=ページやコンテンツの縮尺を変更します。 scalePages.tags=resize,modify,dimension,adapt -home.pipeline.title=パイプライン (高度) +home.pipeline.title=パイプライン home.pipeline.desc=パイプラインスクリプトを定義してPDF上で複数のアクションを実行します。 pipeline.tags=automate,sequence,scripted,batch-process @@ -482,6 +502,11 @@ home.removeImagePdf.title=画像の削除 home.removeImagePdf.desc=PDFから画像を削除してファイルサイズを小さくします removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=アクセス拒否 login.oauth2InvalidTokenResponse=無効なトークン応答 login.oauth2InvalidIdToken=無効なIDトークン login.userIsDisabled=ユーザーは非アクティブ化されており、現在このユーザー名でのログインはブロックされています。管理者に連絡してください。 - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=自動塗りつぶし @@ -1154,6 +1182,8 @@ licenses.license=ライセンス survey.nav=アンケート survey.title=Stirling-PDFのアンケート survey.description=Stirling-PDFには追跡機能がないため、Stirling-PDFをより良くするために皆様の意見を聞かせてください! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=アンケートにご協力ください! survey.disabled=(アンケートのポップアップは、次の更新では無効になりますが、ページの下部に表示されます。) survey.button=アンケートに答える @@ -1179,3 +1209,17 @@ removeImage.title=画像の削除 removeImage.header=画像の削除 removeImage.removeImage=画像の削除 removeImage.submit=画像を削除 + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_ko_KR.properties b/src/main/resources/messages_ko_KR.properties index 43d8f84ec00..3241999242f 100644 --- a/src/main/resources/messages_ko_KR.properties +++ b/src/main/resources/messages_ko_KR.properties @@ -76,6 +76,7 @@ donate=기부하기 color=색상 sponsor=스폰서 info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=파이프라인: pipelineOptions.saveButton=다운로드 pipelineOptions.validateButton=확인 - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF navbar.sections.security=Sign & Security navbar.sections.advance=Advanced navbar.sections.edit=View & Edit +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Access Denied login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidIdToken=Invalid Id Token login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=자동 검열 @@ -1154,6 +1182,8 @@ licenses.license=라이센스 survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Please consider taking our survey! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_nl_NL.properties b/src/main/resources/messages_nl_NL.properties index 4d1469d8bc8..b21d3dd21a3 100644 --- a/src/main/resources/messages_nl_NL.properties +++ b/src/main/resources/messages_nl_NL.properties @@ -76,6 +76,7 @@ donate=Doneer color=Kleur sponsor=Sponsor info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pijplijn: pipelineOptions.saveButton=Downloaden pipelineOptions.validateButton=Valideren - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Converteren van PDF navbar.sections.security=Ondertekenen & beveiliging navbar.sections.advance=Geavanceerd navbar.sections.edit=Bekijken & wijzigen +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -382,7 +402,7 @@ home.scalePages.title=Aanpassen paginaformaat/schaal home.scalePages.desc=Wijzig de grootte/schaal van een pagina en/of de inhoud ervan. scalePages.tags=resize,aanpassen,dimensie,aanpassen -home.pipeline.title=Pijplijn (Geavanceerd) +home.pipeline.title=Pijplijn home.pipeline.desc=Voer meerdere acties uit op PDF's door pipelinescripts te definiëren pipeline.tags=automatiseren,volgorde,gescrript,batch-verwerking @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Toegang geweigerd login.oauth2InvalidTokenResponse=Ongeldige tokenreactie login.oauth2InvalidIdToken=Ongeldige ID token login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Automatisch censureren @@ -1154,6 +1182,8 @@ licenses.license=Licentie survey.nav=Enquête survey.title=Stirling-PDF Enquête survey.description=Stirling-PDF heeft geen tracking, dus we willen van onze gebruikers horen om Stirling-PDF te verbeteren. +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Overweeg alstublieft om onze enquête in te vullen! survey.disabled=(Enquête popup wordt in een toekomstige update weggehaald, maar is beschikbaar aan de onderkant van de pagina.) survey.button=Vul enquête in. @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_no_NB.properties b/src/main/resources/messages_no_NB.properties index abd0738a3a2..64152299505 100644 --- a/src/main/resources/messages_no_NB.properties +++ b/src/main/resources/messages_no_NB.properties @@ -76,6 +76,7 @@ donate=Doner color=Farge sponsor=Sponsor info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Last ned pipelineOptions.validateButton=Valider - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Konverter fra PDF navbar.sections.security=Signer & Sikkerhet navbar.sections.advance=Avansert navbar.sections.edit=Vis & Rediger +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=Fil ikke funnet database.fileNullOrEmpty=Fil må ikke være tom eller null database.failedImportFile=Import av fil mislyktes +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Tilgang nektet login.oauth2InvalidTokenResponse=Ugyldig tokenrespons login.oauth2InvalidIdToken=Ugyldig Id Token login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Automatisk Sensurering @@ -1154,6 +1182,8 @@ licenses.license=Lisens survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Please consider taking our survey! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_pl_PL.properties b/src/main/resources/messages_pl_PL.properties index c15170cbdcd..e252dad69d0 100755 --- a/src/main/resources/messages_pl_PL.properties +++ b/src/main/resources/messages_pl_PL.properties @@ -76,6 +76,7 @@ donate=Podaruj color=kolor sponsor=sponsor info=informacje +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Automatyzacja pipelineOptions.saveButton=Pobierz pipelineOptions.validateButton=Waliduj - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Przetwórz z PDF navbar.sections.security=Podpis i bezpieczeństwo navbar.sections.advance=Zaawansowane navbar.sections.edit=Podgląd i edycja +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=Plik nie znaleziony database.fileNullOrEmpty=Plik nie może być pusty database.failedImportFile=Nie udało się zaimportować pliku +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -382,7 +402,7 @@ home.scalePages.title=Dopasuj rozmiar stron home.scalePages.desc=Dopasuj rozmiar stron wybranego dokumentu PDF scalePages.tags=resize,modify,dimension,adapt -home.pipeline.title=Automatyzacja (Zaawansowane) +home.pipeline.title=Automatyzacja home.pipeline.desc=Wykonaj wiele akcji na dokumentach PDF, tworząc automatyzację pipeline.tags=automate,sequence,scripted,batch-process @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Brak dostępu login.oauth2InvalidTokenResponse=Nieprawidłowa odpowiedź na token login.oauth2InvalidIdToken=Nieprawidłowa wartość tokenu login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Automatyczne zaciemnienie @@ -1154,6 +1182,8 @@ licenses.license=Licencja survey.nav=Ankieta survey.title=Ankieta Stirling-PDF survey.description=Stirling-PDF nie śledzi swoich użytkowników, więc chciałby poznać opinie swoich użytkowników! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Wypełnij proszę ankietę dla nas! survey.disabled=(Blokada wyskakującego okienka z ankieta zostanie dodane w następnych aktualizacjach, ale będzie dostępna na dole strony) survey.button=Wypełnij ankietę @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_pt_BR.properties b/src/main/resources/messages_pt_BR.properties index b1fc9dee210..1c809b8a298 100644 --- a/src/main/resources/messages_pt_BR.properties +++ b/src/main/resources/messages_pt_BR.properties @@ -76,6 +76,7 @@ donate=Doar color=Cor sponsor=Patrocine info=Informações +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Baixar pipelineOptions.validateButton=Validar - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Converter de PDF navbar.sections.security=Assinatura & Segurança navbar.sections.advance=Avançado navbar.sections.edit=Visualizar & editar +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=Arquivo não encontrado database.fileNullOrEmpty=O arquivo não estar nulo ou vazio database.failedImportFile=Falha ao importar arquivo +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -382,7 +402,7 @@ home.scalePages.title=Ajustar Tamanho/Escala de Página home.scalePages.desc=Alterar o tamanho/escala da página e/ou seu conteúdo. scalePages.tags=redimensionar,modificar,dimensão,adaptar -home.pipeline.title=Pipeline (Avançado) +home.pipeline.title=Pipeline home.pipeline.desc=Executar várias ações em PDFs definindo scripts de pipeline pipeline.tags=automatizar,sequência,scriptado,processo-em-lote @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remover imagem home.removeImagePdf.desc=Remova a imagem do PDF para reduzir o tamanho do arquivo removeImagePdf.tags=Remover imagem,operações de página,back-end,lado do servidor + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Acesso negado login.oauth2InvalidTokenResponse=Resposta de token inválida login.oauth2InvalidIdToken=Id de token inválido login.userIsDisabled=O usuário está desativado, o login está atualmente bloqueado com este nome de usuário. Entre em contato com o administrador. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Redigir automaticamente @@ -1154,6 +1182,8 @@ licenses.license=Licença survey.nav=Pesquisa survey.title=Pesquisa Stirling-PDF survey.description=Stirling-PDF não tem rastreamento, então queremos ouvir nossos usuários para melhorar o Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Por favor, considere responder à nossa pesquisa! survey.disabled=(O pop-up da pesquisa será desativado nas atualizações seguintes, mas estará disponível no final da página) survey.button=Responda a pesquisa @@ -1179,3 +1209,17 @@ removeImage.title=Remover imagem removeImage.header=Remover imagem removeImage.removeImage=Remover imagem removeImage.submit=Remover imagem + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_pt_PT.properties b/src/main/resources/messages_pt_PT.properties index 7f6e2956828..60f96104f9d 100644 --- a/src/main/resources/messages_pt_PT.properties +++ b/src/main/resources/messages_pt_PT.properties @@ -76,6 +76,7 @@ donate=Donate color=Color sponsor=Sponsor info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Descarregar pipelineOptions.validateButton=Validar - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF navbar.sections.security=Sign & Security navbar.sections.advance=Advanced navbar.sections.edit=View & Edit +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Access Denied login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidIdToken=Invalid Id Token login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Edição Automática @@ -1154,6 +1182,8 @@ licenses.license=Licença survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Please consider taking our survey! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_ro_RO.properties b/src/main/resources/messages_ro_RO.properties index f065256d678..0fdb5695f35 100644 --- a/src/main/resources/messages_ro_RO.properties +++ b/src/main/resources/messages_ro_RO.properties @@ -76,6 +76,7 @@ donate=Donează color=Culoare sponsor=Sponsor info=Informații +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Descarcă pipelineOptions.validateButton=Validează - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Convertește din PDF navbar.sections.security=Semnează & Securitate navbar.sections.advance=Avansat navbar.sections.edit=Vizualizează & Editează +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=Fișierul nu a fost găsit database.fileNullOrEmpty=Fișierul nu trebuie să fie nul sau gol database.failedImportFile=Importul Fișierului a Eșuat +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Elimină imagine home.removeImagePdf.desc=Elimină imaginea din PDF pentru a reduce dimensiunea fișierului removeImagePdf.tags=Elimină Imagine,Operații pagină,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Acces Refuzat login.oauth2InvalidTokenResponse=Răspuns Invalid la Token login.oauth2InvalidIdToken=Token de Id Invalid login.userIsDisabled=Utilizatorul este dezactivat, conectarea este în prezent blocată cu acest nume de utilizator. Te rugăm să contactezi administratorul. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Redactare Automată @@ -1154,6 +1182,8 @@ licenses.license=Licență survey.nav=Sondaj survey.title=Sondaj Stirling-PDF survey.description=Stirling-PDF nu are urmărire, așa că vrem să auzim de la utilizatorii noștri pentru a îmbunătăți Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Te rugăm să iei în considerare completarea sondajului nostru! survey.disabled=(Fereastra pop-up a sondajului va fi dezactivată în următoarele actualizări, dar va fi disponibilă în subsolul paginii) survey.button=Completează Sondajul @@ -1179,3 +1209,17 @@ removeImage.title=Elimină imagine removeImage.header=Elimină imagine removeImage.removeImage=Elimină imagine removeImage.submit=Elimină imagine + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_ru_RU.properties b/src/main/resources/messages_ru_RU.properties index b5db3c478c5..1ac98ac331f 100644 --- a/src/main/resources/messages_ru_RU.properties +++ b/src/main/resources/messages_ru_RU.properties @@ -76,6 +76,7 @@ donate=Пожертвовать color=Цвет sponsor=Спонсор info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Конвейер: pipelineOptions.saveButton=Скачать pipelineOptions.validateButton=Проверить - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF navbar.sections.security=Sign & Security navbar.sections.advance=Advanced navbar.sections.edit=View & Edit +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Access Denied login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidIdToken=Invalid Id Token login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Автоматическое редактирование @@ -1154,6 +1182,8 @@ licenses.license=Лицензия survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Please consider taking our survey! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_sk_SK.properties b/src/main/resources/messages_sk_SK.properties index d64aaeaf8ae..b1a3d568ff1 100644 --- a/src/main/resources/messages_sk_SK.properties +++ b/src/main/resources/messages_sk_SK.properties @@ -76,6 +76,7 @@ donate=Darovať color=Farba sponsor=Sponzorovať info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Stiahnuť pipelineOptions.validateButton=Overiť - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF navbar.sections.security=Sign & Security navbar.sections.advance=Advanced navbar.sections.edit=View & Edit +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -382,7 +402,7 @@ home.scalePages.title=Prispôsobiť veľkosť/škálovanie stránok home.scalePages.desc=Zmeniť veľkosť/škálovanie stránky a/alebo jej obsahu. scalePages.tags=veľkosť,modifikovať,rozmery,prispôsobiť -home.pipeline.title=Pipeline (Pokročilé) +home.pipeline.title=Pipeline home.pipeline.desc=Spustiť viacero akcií na PDF definovaním pipeline skriptov pipeline.tags=automatizovať,sekvencia,skriptované,dávkové spracovanie @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Access Denied login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidIdToken=Invalid Id Token login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Automatické redigovanie @@ -1154,6 +1182,8 @@ licenses.license=Licencia survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Please consider taking our survey! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_sr_LATN_RS.properties b/src/main/resources/messages_sr_LATN_RS.properties index 53bb42300a4..a96a510ae4a 100644 --- a/src/main/resources/messages_sr_LATN_RS.properties +++ b/src/main/resources/messages_sr_LATN_RS.properties @@ -76,6 +76,7 @@ donate=Donate color=Color sponsor=Sponsor info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Preuzmi pipelineOptions.validateButton=Proveri - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF navbar.sections.security=Sign & Security navbar.sections.advance=Advanced navbar.sections.edit=View & Edit +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Access Denied login.oauth2InvalidTokenResponse=Invalid Token Response login.oauth2InvalidIdToken=Invalid Id Token login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Auto Cenzura @@ -1154,6 +1182,8 @@ licenses.license=License survey.nav=Survey survey.title=Stirling-PDF Survey survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Please consider taking our survey! survey.disabled=(Survey popup will be disabled in following updates but available at foot of page) survey.button=Take Survey @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_sv_SE.properties b/src/main/resources/messages_sv_SE.properties index a85d294ce91..e42ae8785ae 100644 --- a/src/main/resources/messages_sv_SE.properties +++ b/src/main/resources/messages_sv_SE.properties @@ -76,6 +76,7 @@ donate=Donera color=Färg sponsor=Sponsor info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Ladda ner pipelineOptions.validateButton=Validera - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Konvertera från PDF navbar.sections.security=Signera & Säkerhet navbar.sections.advance=Avancerat navbar.sections.edit=Visa & Redigera +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=Filen hittades inte database.fileNullOrEmpty=Filen får inte vara null eller tom database.failedImportFile=Misslyckades med att importera fil +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Ta bort bild home.removeImagePdf.desc=Ta bort bild från PDF för att minska filstorlek removeImagePdf.tags=Ta bort bild,Sidoperationer,Backend,serversida + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Åtkomst nekad login.oauth2InvalidTokenResponse=Ogiltigt token-svar login.oauth2InvalidIdToken=Ogiltigt Id-token login.userIsDisabled=Användaren är inaktiverad, inloggning är för närvarande blockerad med detta användarnamn. Kontakta administratören. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Auto-redigera @@ -1154,6 +1182,8 @@ licenses.license=Licens survey.nav=Undersökning survey.title=Stirling-PDF-undersökning survey.description=Stirling-PDF har ingen spårning så vi vill höra från våra användare för att förbättra Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Vänligen överväg att delta i vår undersökning! survey.disabled=(Undersökningspopup kommer att inaktiveras i kommande uppdateringar men finns tillgänglig längst ner på sidan) survey.button=Delta i undersökningen @@ -1179,3 +1209,17 @@ removeImage.title=Ta bort bild removeImage.header=Ta bort bild removeImage.removeImage=Ta bort bild removeImage.submit=Ta bort bild + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_th_TH.properties b/src/main/resources/messages_th_TH.properties index 69f93721a52..9623bf2e194 100644 --- a/src/main/resources/messages_th_TH.properties +++ b/src/main/resources/messages_th_TH.properties @@ -76,6 +76,7 @@ donate=บริจาค color=สี sponsor=ผู้สนับสนุน info=ข้อมูล +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=ดาวน์โหลด pipelineOptions.validateButton=ตรวจสอบความถูกต้อง - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=แปลงจาก PDF navbar.sections.security=ลงนามและความปลอดภัย navbar.sections.advance=ขั้นสูง navbar.sections.edit=ดูและแก้ไข +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=ไม่พบไฟล์ database.fileNullOrEmpty=ไฟล์ต้องไม่ว่างเปล่าหรือไม่มีข้อมูล database.failedImportFile=การนำเข้าไฟล์ล้มเหลว +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=การเข้าถึงถูกปฏิเส login.oauth2InvalidTokenResponse=การตอบกลับโทเค็นไม่ถูกต้อง login.oauth2InvalidIdToken=โทเค็น Id ไม่ถูกต้อง login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=ซ่อนข้อมูลอัตโนมัติ @@ -1154,6 +1182,8 @@ licenses.license=ใบอนุญาต survey.nav=สำรวจ survey.title=สำรวจ Stirling-PDF survey.description=Stirling-PDF ไม่มีการติดตาม ดังนั้นเราต้องการฟังความคิดเห็นจากผู้ใช้เพื่อปรับปรุง Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=กรุณาพิจารณาการสำรวจของเรา! survey.disabled=(ป๊อปอัปการสำรวจจะถูกปิดใช้งานในการอัปเดตต่อไปนี้ แต่สามารถใช้ได้ที่ส่วนท้ายของหน้า) survey.button=เริ่มสำรวจ @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_tr_TR.properties b/src/main/resources/messages_tr_TR.properties index ad3a5339a39..88cade1266d 100644 --- a/src/main/resources/messages_tr_TR.properties +++ b/src/main/resources/messages_tr_TR.properties @@ -76,6 +76,7 @@ donate=Bağış Yapın color=Renk sponsor=Bağış info=Bilgi +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Çoklu İşlemler: pipelineOptions.saveButton=İndir pipelineOptions.validateButton=Doğrula - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=PDF'den dönüştür navbar.sections.security=Oturum ve Güvenlik navbar.sections.advance=Gelişmiş navbar.sections.edit=Görüntüle ve Düzenle +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=Dosya bulunamadı database.fileNullOrEmpty=Dosya yok veya boş olmamalıdır database.failedImportFile=Dosya İçe Aktarılamadı +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -382,7 +402,7 @@ home.scalePages.title=Sayfa boyutunu/ölçeğini ayarla home.scalePages.desc=Bir sayfanın ve/veya içeriğinin boyutunu/ölçeğini değiştirir scalePages.tags=boyutlandır,değiştir,boyut,uyarla -home.pipeline.title=Çoklu İşlemler (İleri Seviye) +home.pipeline.title=Çoklu İşlemler home.pipeline.desc=Çoklu İşlemler tanımlayarak PDF'lere birden fazla işlemi çalıştır pipeline.tags=otomatikleştir,sıralı,betikli,toplu-işlem @@ -482,6 +502,11 @@ home.removeImagePdf.title=Resmi kaldır home.removeImagePdf.desc=Dosya boyutunu küçültmek için PDF'den resmi kaldırın removeImagePdf.tags=Resmi Kaldır,Sayfa İşlemleri,Arka uç,sunucu tarafı + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Erişim Reddedildi login.oauth2InvalidTokenResponse=Geçersiz Belirteç Yanıtı login.oauth2InvalidIdToken=Geçersiz Kimlik Belirteci login.userIsDisabled=Kullanıcı devre dışı bırakıldı, şu anda bu kullanıcı adıyla giriş engellendi. Lütfen yöneticiyle iletişime geçin. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Otomatik Karartma @@ -1154,6 +1182,8 @@ licenses.license=Lisans survey.nav=Anket survey.title=Stirling-PDF Anketi survey.description=Stirling-PDF'te izleme yok, bu yüzden Stirling-PDF'i iyileştirmek için kullanıcılarımızdan geri bildirim almak istiyoruz! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Lütfen anketimize katılmayı düşünün! survey.disabled=(Anket açılır penceresi sonraki güncellemelerde devre dışı bırakılacak ancak sayfanın alt kısmında yer alacaktır) survey.button=Ankete Katıl @@ -1179,3 +1209,17 @@ removeImage.title=Resmi kaldır removeImage.header=Resmi kaldır removeImage.removeImage=Resmi kaldır removeImage.submit=Resmi kaldır + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_uk_UA.properties b/src/main/resources/messages_uk_UA.properties index ed35d4e6684..b2b606119c9 100644 --- a/src/main/resources/messages_uk_UA.properties +++ b/src/main/resources/messages_uk_UA.properties @@ -76,6 +76,7 @@ donate=Задонатити color=Колір sponsor=Спонсор info=Інформація +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Конвеєр: pipelineOptions.saveButton=Завантажити pipelineOptions.validateButton=Перевірити - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Конвертувати з PDF navbar.sections.security=Підпис та Безпека navbar.sections.advance=Додаткове navbar.sections.edit=Перегляд та Редагування +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=File not Found database.fileNullOrEmpty=File must not be null or empty database.failedImportFile=Failed Import File +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Доступ заблоковано login.oauth2InvalidTokenResponse=Недійсна відповідь з токеном login.oauth2InvalidIdToken=Недійсний Id токен login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Автоматичне редагування @@ -1154,6 +1182,8 @@ licenses.license=Ліцензія survey.nav=Опитування survey.title=Опитування Stirling-PDF survey.description=Stirling-PDF не має аналітичних засобів для відслідковування, тому ми хочемо почути думку від користувачів, як покращити Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Будь-ласка, пройдіть опитування! survey.disabled=(Вікно з опитування буде відключено у наступних оновленнях, але буде доступне внизу сторінки) survey.button=Пройти опитування @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_vi_VN.properties b/src/main/resources/messages_vi_VN.properties index c12a40d530e..9b48912c635 100644 --- a/src/main/resources/messages_vi_VN.properties +++ b/src/main/resources/messages_vi_VN.properties @@ -76,6 +76,7 @@ donate=Ủng hộ color=Màu sắc sponsor=Nhà tài trợ info=Thông tin +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Tải xuống pipelineOptions.validateButton=Xác thực - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=Chuyển đổi từ PDF navbar.sections.security=Ký & Bảo mật navbar.sections.advance=Nâng cao navbar.sections.edit=Xem & Chỉnh sửa +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=Không tìm thấy tệp database.fileNullOrEmpty=Tệp không được để trống hoặc rỗng database.failedImportFile=Không thể nhập tệp +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=Truy cập bị từ chối login.oauth2InvalidTokenResponse=Phản hồi token không hợp lệ login.oauth2InvalidIdToken=Id Token không hợp lệ login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=Tự động biên tập @@ -1154,6 +1182,8 @@ licenses.license=Giấy phép survey.nav=Khảo sát survey.title=Khảo sát Stirling-PDF survey.description=Stirling-PDF không có cài đặt theo dõi nên chúng tôi muốn nghe từ người dùng để cải thiện Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=Vui lòng cân nhắc tham gia khảo sát của chúng tôi! survey.disabled=(Cửa sổ popup khảo sát sẽ bị vô hiệu hóa trong các bản cập nhật tiếp theo nhưng vẫn tìm thấy ở cuối trang) survey.button=Tham gia khảo sát @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_zh_CN.properties b/src/main/resources/messages_zh_CN.properties index 4ddf1e87c94..d30edd39d4f 100644 --- a/src/main/resources/messages_zh_CN.properties +++ b/src/main/resources/messages_zh_CN.properties @@ -76,6 +76,7 @@ donate=捐款 color=颜色 sponsor=赞助 info=信息 +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=流水线: pipelineOptions.saveButton=下载 pipelineOptions.validateButton=验证 - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=从PDF转换 navbar.sections.security=签名和安全 navbar.sections.advance=高级功能 navbar.sections.edit=查看和编辑 +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=未找到文件 database.fileNullOrEmpty=文件不能为空 database.failedImportFile=导入文件失败 +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=删除图像 home.removeImagePdf.desc=删除图像减少PDF大小 removeImagePdf.tags=删除图像, 页面操作, 后端, 服务端 + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=拒绝访问 login.oauth2InvalidTokenResponse=无效的Token响应 login.oauth2InvalidIdToken=无效的Token login.userIsDisabled=用户被禁用,登录已被阻止。请联系管理员。 - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=自动删除 @@ -1154,6 +1182,8 @@ licenses.license=许可证 survey.nav=调查 survey.title=Stirling-PDF调查 survey.description=Stirling-PDF没有跟踪器,所以我们希望听取用户的意见来改进Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=请考虑参加我们的调查! survey.disabled=(调查弹出窗口将在后续更新中被禁用,但可在页脚处查看) survey.button=参与调查 @@ -1179,3 +1209,17 @@ removeImage.title=删除图像 removeImage.header=删除图像 removeImage.removeImage=删除图像 removeImage.submit=删除图像 + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/messages_zh_TW.properties b/src/main/resources/messages_zh_TW.properties index 9e912b554b3..3f5f50f44f0 100644 --- a/src/main/resources/messages_zh_TW.properties +++ b/src/main/resources/messages_zh_TW.properties @@ -76,6 +76,7 @@ donate=捐贈 color=顏色 sponsor=贊助 info=Info +pro=Pro page=Page pages=Pages @@ -110,8 +111,24 @@ pipelineOptions.pipelineHeader=管道: pipelineOptions.saveButton=下載 pipelineOptions.validateButton=驗證 - - +######################## +# ENTERPRISE EDITION # +######################## +enterpriseEdition.button=Upgrade to Pro +enterpriseEdition.warning=This feature is only available to Pro users. +enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. +enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro + + +################# +# Analytics # +################# +analytics.title=Do you want make Stirling PDF better? +analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. +analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. +analytics.enable=Enable analytics +analytics.disable=Disable analytics +analytics.settings=You can change the settings for analytics in the config/settings.yml file ############# # NAVBAR # @@ -128,6 +145,7 @@ navbar.sections.convertFrom=從 PDF 轉換 navbar.sections.security=簽章與安全性 navbar.sections.advance=進階 navbar.sections.edit=檢視與編輯 +navbar.sections.popular=Popular ############# # SETTINGS # @@ -225,6 +243,8 @@ database.fileNotFound=檔案未找到 database.fileNullOrEmpty=檔案不能為空或空白 database.failedImportFile=匯入檔案失敗 +session.expired=Your session has expired. Please refresh the page and try again. + ############# # HOME-PAGE # ############# @@ -482,6 +502,11 @@ home.removeImagePdf.title=Remove image home.removeImagePdf.desc=Remove image from PDF to reduce file size removeImagePdf.tags=Remove Image,Page operations,Back end,server side + +home.splitPdfByChapters.title=Split PDF by Chapters +home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. +splitPdfByChapters.tags=split,chapters,bookmarks,organize + #replace-invert-color replace-color.title=Replace-Invert-Color replace-color.header=Replace-Invert Color PDF @@ -526,7 +551,10 @@ login.oauth2AccessDenied=存取被拒 login.oauth2InvalidTokenResponse=無效的 Token 回應 login.oauth2InvalidIdToken=無效的 Tokne login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. - +login.alreadyLoggedIn=You are already logged in to +login.alreadyLoggedIn2=devices. Please log out of the devices and try again. +login.toManySessions=You have too many active sessions +login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. #auto-redact autoRedact.title=自動塗黑 @@ -1154,6 +1182,8 @@ licenses.license=許可證 survey.nav=問卷調查 survey.title=Stirling-PDF 問卷調查 survey.description=Stirling-PDF 沒有追蹤功能,所以我們希望聽取用戶的意見來改進 Stirling-PDF! +survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: +survey.changes2=With these changes we are getting paid business support and funding survey.please=請考慮參加我們的問卷調查! survey.disabled=(問卷調查彈出窗口將在後續更新中停用,但仍可在頁腳處使用) survey.button=參加問卷調查 @@ -1179,3 +1209,17 @@ removeImage.title=Remove image removeImage.header=Remove image removeImage.removeImage=Remove image removeImage.submit=Remove image + + +splitByChapters.title=Split PDF by Chapters +splitByChapters.header=Split PDF by Chapters +splitByChapters.bookmarkLevel=Bookmark Level +splitByChapters.includeMetadata=Include Metadata +splitByChapters.allowDuplicates=Allow Duplicates +splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. +splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). +splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. +splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.submit=Split PDF + + diff --git a/src/main/resources/settings.yml.template b/src/main/resources/settings.yml.template index 2a5400b6018..cd2481128e4 100644 --- a/src/main/resources/settings.yml.template +++ b/src/main/resources/settings.yml.template @@ -49,7 +49,8 @@ security: provider: google # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak' # Enterprise edition settings unused for now please ignore! -EnterpriseEdition: +enterpriseEdition: + enabled: false # set to 'true' to enable enterprise edition key: 00000000-0000-0000-0000-000000000000 CustomMetadata: autoUpdateMetadata: true # set to 'true' to automatically update metadata with below values @@ -72,6 +73,7 @@ system: showUpdateOnlyAdmin: false # Only admins can see when a new update is available, depending on showUpdate it must be set to 'true' customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template html files tessdataDir: /usr/share/tessdata # Path to the directory containing the Tessdata files. This setting is relevant for Windows systems. For Windows users, this path should be adjusted to point to the appropriate directory where the Tessdata files are stored. + enableAnalytics: undefined # Set to 'true' to enable analytics, set to 'false' to disable analytics, for enterprise users this is set to true ui: appName: '' # Application's visible name @@ -88,3 +90,4 @@ metrics: # Automatically Generated Settings (Do Not Edit Directly) AutomaticallyGenerated: key: example + UUID: example diff --git a/src/main/resources/static/css/bootstrap.min.css b/src/main/resources/static/css/bootstrap.min.css index 92e022d3d45..5c4d3c11ac6 100644 --- a/src/main/resources/static/css/bootstrap.min.css +++ b/src/main/resources/static/css/bootstrap.min.css @@ -3,5 +3,5 @@ * Copyright 2011-2021 The Bootstrap Authors * Copyright 2011-2021 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0))}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-font-sans-serif);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x) * -.5);margin-left:calc(var(--bs-gutter-x) * -.5)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + (.5rem + 2px));padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + (1rem + 2px));padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + (.75rem + 2px))}textarea.form-control-sm{min-height:calc(1.5em + (.5rem + 2px))}textarea.form-control-lg{min-height:calc(1.5em + (1rem + 2px))}.form-control-color{max-width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast:not(.showing):not(.show){opacity:0}.toast.hide{display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1060;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:600px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1050;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{color:#0d6efd!important}.text-secondary{color:#6c757d!important}.text-success{color:#198754!important}.text-info{color:#0dcaf0!important}.text-warning{color:#ffc107!important}.text-danger{color:#dc3545!important}.text-light{color:#f8f9fa!important}.text-dark{color:#212529!important}.text-white{color:#fff!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-reset{color:inherit!important}.bg-primary{background-color:#0d6efd!important}.bg-secondary{background-color:#6c757d!important}.bg-success{background-color:#198754!important}.bg-info{background-color:#0dcaf0!important}.bg-warning{background-color:#ffc107!important}.bg-danger{background-color:#dc3545!important}.bg-light{background-color:#f8f9fa!important}.bg-dark{background-color:#212529!important}.bg-body{background-color:#fff!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} + */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0))}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-font-sans-serif);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1340px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x) * -.5);margin-left:calc(var(--bs-gutter-x) * -.5)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + (.5rem + 2px));padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + (1rem + 2px));padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + (.75rem + 2px))}textarea.form-control-sm{min-height:calc(1.5em + (.5rem + 2px))}textarea.form-control-lg{min-height:calc(1.5em + (1rem + 2px))}.form-control-color{max-width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast:not(.showing):not(.show){opacity:0}.toast.hide{display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1060;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:600px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1050;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{color:#0d6efd!important}.text-secondary{color:#6c757d!important}.text-success{color:#198754!important}.text-info{color:#0dcaf0!important}.text-warning{color:#ffc107!important}.text-danger{color:#dc3545!important}.text-light{color:#f8f9fa!important}.text-dark{color:#212529!important}.text-white{color:#fff!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-reset{color:inherit!important}.bg-primary{background-color:#0d6efd!important}.bg-secondary{background-color:#6c757d!important}.bg-success{background-color:#198754!important}.bg-info{background-color:#0dcaf0!important}.bg-warning{background-color:#ffc107!important}.bg-danger{background-color:#dc3545!important}.bg-light{background-color:#f8f9fa!important}.bg-dark{background-color:#212529!important}.bg-body{background-color:#fff!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} /*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/src/main/resources/static/css/multi-tool.css b/src/main/resources/static/css/multi-tool.css index 295659086ce..9687470a899 100644 --- a/src/main/resources/static/css/multi-tool.css +++ b/src/main/resources/static/css/multi-tool.css @@ -77,7 +77,7 @@ label { flex-direction: column; padding: 1rem; border-radius: 25px; - overflow-y: hidden; + overflow-y: auto; overflow-x: auto; min-height: 275px; margin: 0 0 30px 0; diff --git a/src/main/resources/static/css/navbar.css b/src/main/resources/static/css/navbar.css index 7db824628c3..659940b9133 100644 --- a/src/main/resources/static/css/navbar.css +++ b/src/main/resources/static/css/navbar.css @@ -89,6 +89,14 @@ width: 80%; } +.close-icon { + color: var(--md-sys-color-secondary); +} + +.close-icon:hover { + transform: scale(1.15); +} + span.icon-text::after { content: attr(data-text); content: attr(data-text) / ""; @@ -320,3 +328,26 @@ span.icon-text::after { display: none; } } + + .go-pro-link { + position: relative; + padding: 0.5rem 1rem; + transition: all 0.3s ease; + } + +.go-pro-badge { + display: inline-block; + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + font-weight: bold; + color: #ffffff; + background-color: #007bff; + border-radius: 0.25rem; + text-transform: uppercase; + transition: all 0.3s ease; +} + +.go-pro-link:hover .go-pro-badge { + background-color: #0056b3; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} \ No newline at end of file diff --git a/src/main/resources/static/css/rotate-pdf.css b/src/main/resources/static/css/rotate-pdf.css index 57a30024d82..19024e2071f 100644 --- a/src/main/resources/static/css/rotate-pdf.css +++ b/src/main/resources/static/css/rotate-pdf.css @@ -27,3 +27,16 @@ display: flex; justify-content: space-around; } + +#pdf-preview-large { + margin: 0 auto; + display: block; + max-width: calc(100% - 30px); + max-height: calc(100% - 30px); + box-shadow: 0 0 4px rgba(100, 100, 100, 0.25); + transition: rotate 0.3s; + position: absolute; + top: 50%; + left: 50%; + translate: -50% -50%; +} diff --git a/src/main/resources/static/js/downloader.js b/src/main/resources/static/js/downloader.js index 46e6a94833b..bbe16ba5266 100644 --- a/src/main/resources/static/js/downloader.js +++ b/src/main/resources/static/js/downloader.js @@ -5,6 +5,22 @@ function showErrorBanner(message, stackTrace) { document.querySelector("#errorContainer p").textContent = message; document.querySelector("#traceContent").textContent = stackTrace; } + +function showSessionExpiredPrompt() { + const errorContainer = document.getElementById("errorContainer"); + errorContainer.style.display = "block"; + document.querySelector("#errorContainer .alert-heading").textContent = sessionExpired; + document.querySelector("#errorContainer p").textContent = sessionExpired; + document.querySelector("#traceContent").textContent = ""; + + // Optional: Add a refresh button + const refreshButton = document.createElement("button"); + refreshButton.textContent = "Refresh Page"; + refreshButton.className = "btn btn-primary mt-3"; + refreshButton.onclick = () => location.reload(); + errorContainer.appendChild(refreshButton); +} + let firstErrorOccurred = false; $(document).ready(function () { @@ -79,6 +95,11 @@ async function handleSingleDownload(url, formData, isMulti = false, isZip = fals const contentType = response.headers.get("content-type"); if (!response.ok) { + if (response.status === 401) { + // Handle 401 Unauthorized error + showSessionExpiredPrompt(); + return; + } if (contentType && contentType.includes("application/json")) { console.error("Throwing error banner, response was not okay"); return handleJsonResponse(response); @@ -97,7 +118,7 @@ async function handleSingleDownload(url, formData, isMulti = false, isZip = fals } } catch (error) { console.error("Error in handleSingleDownload:", error); - throw error; // Re-throw the error if you want it to be handled higher up. + throw error; } } diff --git a/src/main/resources/static/js/favourites.js b/src/main/resources/static/js/favourites.js index dbecd013f81..16e219f77c8 100644 --- a/src/main/resources/static/js/favourites.js +++ b/src/main/resources/static/js/favourites.js @@ -1,45 +1,73 @@ function updateFavoritesDropdown() { var dropdown = document.querySelector("#favoritesDropdown"); - // Check if dropdown exists if (!dropdown) { console.error('Dropdown element with ID "favoritesDropdown" not found!'); - return; // Exit the function + return; } - dropdown.innerHTML = ""; // Clear the current favorites + dropdown.innerHTML = ""; var hasFavorites = false; + var addedFeatures = new Set(); for (var i = 0; i < localStorage.length; i++) { var key = localStorage.key(i); - if (localStorage.getItem(key) === "favorite") { - // Find the corresponding navbar entry + var value = localStorage.getItem(key); + + if (value === "favorite") { var navbarEntry = document.querySelector(`a[href='${key}']`); if (navbarEntry) { - // Create a new dropdown entry - var dropdownItem = document.createElement("a"); - dropdownItem.className = "dropdown-item"; - dropdownItem.href = navbarEntry.href; - dropdownItem.innerHTML = navbarEntry.innerHTML; - dropdown.appendChild(dropdownItem); - hasFavorites = true; + var featureName = navbarEntry.textContent.trim(); + + if (!addedFeatures.has(featureName)) { + var dropdownItem = document.createElement("div"); + dropdownItem.className = "dropdown-item d-flex justify-content-between align-items-center"; + + // Create a wrapper for the original content + var contentWrapper = document.createElement("div"); + contentWrapper.className = "d-flex align-items-center flex-grow-1"; + contentWrapper.style.textDecoration = "none"; + contentWrapper.style.color = "inherit"; + + // Clone the original content + var originalContent = navbarEntry.querySelector('div').cloneNode(true); + contentWrapper.appendChild(originalContent); + + // Create the remove button + var removeButton = document.createElement("button"); + removeButton.className = "btn btn-sm btn-link p-0 ml-2"; + removeButton.innerHTML = 'close'; + removeButton.onclick = function(itemKey, event) { + event.preventDefault(); + event.stopPropagation(); + localStorage.removeItem(itemKey); + updateFavoritesSection(); + updateFavoritesDropdown(); + filterCards(); + }.bind(null, key); + + // Add click event to the content wrapper + contentWrapper.onclick = function(itemHref, event) { + event.preventDefault(); + window.location.href = itemHref; + }.bind(null, navbarEntry.href); + + dropdownItem.appendChild(contentWrapper); + dropdownItem.appendChild(removeButton); + dropdown.appendChild(dropdownItem); + hasFavorites = true; + addedFeatures.add(featureName); + } } else { console.warn(`Navbar entry not found for key: ${key}`); } } } - // Show or hide the default item based on whether there are any favorites if (!hasFavorites) { var defaultItem = document.createElement("a"); defaultItem.className = "dropdown-item"; - defaultItem.textContent = noFavourites; + defaultItem.textContent = noFavourites || "No favorites added"; dropdown.appendChild(defaultItem); } } - -// Ensure that the DOM content has been fully loaded before calling the function -document.addEventListener("DOMContentLoaded", function () { - console.log("DOMContentLoaded event fired"); - updateFavoritesDropdown(); -}); diff --git a/src/main/resources/static/js/homecard.js b/src/main/resources/static/js/homecard.js index d4dfd7ea42f..efc5314e226 100644 --- a/src/main/resources/static/js/homecard.js +++ b/src/main/resources/static/js/homecard.js @@ -25,29 +25,38 @@ function filterCards() { function updateFavoritesSection() { const favoritesContainer = document.getElementById("groupFavorites").querySelector(".feature-group-container"); favoritesContainer.style.maxHeight = "none"; - favoritesContainer.innerHTML = ""; - const cards = Array.from(document.querySelectorAll(".feature-card")); + favoritesContainer.innerHTML = ""; // Clear the container first + const cards = Array.from(document.querySelectorAll(".feature-card:not(.duplicate)")); + const addedCardIds = new Set(); // To keep track of added card IDs let favoritesAmount = 0; + cards.forEach(card => { - if (localStorage.getItem(card.id) === "favorite") { + if (localStorage.getItem(card.id) === "favorite" && !addedCardIds.has(card.id)) { const duplicate = card.cloneNode(true); + duplicate.classList.add("duplicate"); favoritesContainer.appendChild(duplicate); + addedCardIds.add(card.id); // Mark this card as added favoritesAmount++; } }); + if (favoritesAmount === 0) { document.getElementById("groupFavorites").style.display = "none"; } else { document.getElementById("groupFavorites").style.display = "flex"; - }; + } reorderCards(favoritesContainer); favoritesContainer.style.maxHeight = favoritesContainer.scrollHeight + "px"; -}; +} function toggleFavorite(element) { var span = element.querySelector("span.material-symbols-rounded"); var card = element.closest(".feature-card"); var cardId = card.id; + + // Prevent the event from bubbling up to parent elements + event.stopPropagation(); + if (span.classList.contains("no-fill")) { span.classList.remove("no-fill"); span.classList.add("fill"); @@ -59,7 +68,31 @@ function toggleFavorite(element) { card.classList.remove("favorite"); localStorage.removeItem(cardId); } - reorderCards(card.parentNode); + + // Use setTimeout to ensure this runs after the current call stack is clear + setTimeout(() => { + reorderCards(card.parentNode); + updateFavoritesSection(); + updateFavoritesDropdown(); + filterCards(); + }, 0); +} + +function syncFavorites() { + const cards = Array.from(document.querySelectorAll(".feature-card")); + cards.forEach(card => { + const isFavorite = localStorage.getItem(card.id) === "favorite"; + const starIcon = card.querySelector(".favorite-icon span.material-symbols-rounded"); + if (isFavorite) { + starIcon.classList.remove("no-fill"); + starIcon.classList.add("fill"); + card.classList.add("favorite"); + } else { + starIcon.classList.remove("fill"); + starIcon.classList.add("no-fill"); + card.classList.remove("favorite"); + } + }); updateFavoritesSection(); updateFavoritesDropdown(); filterCards(); @@ -181,7 +214,10 @@ function expandCollapseAll(expandAll) { }) } -window.onload = initializeCards; +window.onload = function() { + initializeCards(); + syncFavorites(); // Ensure everything is in sync on page load +}; document.addEventListener("DOMContentLoaded", function () { const materialIcons = new FontFaceObserver('Material Symbols Rounded'); @@ -223,7 +259,5 @@ document.addEventListener("DOMContentLoaded", function () { }) }, 500); - - showFavoritesOnly(); -}); +}); \ No newline at end of file diff --git a/src/main/resources/static/js/multitool/PdfActionsManager.js b/src/main/resources/static/js/multitool/PdfActionsManager.js index 3701266e2d4..033b399191e 100644 --- a/src/main/resources/static/js/multitool/PdfActionsManager.js +++ b/src/main/resources/static/js/multitool/PdfActionsManager.js @@ -192,6 +192,7 @@ class PdfActionsManager { return div; } + } export default PdfActionsManager; diff --git a/src/main/resources/static/js/multitool/PdfContainer.js b/src/main/resources/static/js/multitool/PdfContainer.js index 55635c69e8c..0cc1a110738 100644 --- a/src/main/resources/static/js/multitool/PdfContainer.js +++ b/src/main/resources/static/js/multitool/PdfContainer.js @@ -117,6 +117,7 @@ class PdfContainer { const newAngle = lastAngle + deg; element.style.rotate = newAngle + "deg"; + } async addPdfFile(file, nextSiblingElement) { @@ -326,6 +327,9 @@ class PdfContainer { page.setRotation(PDFLib.degrees(page.getRotation().angle + rotationAngle)); } } + pdfDoc.setCreator(stirlingPDFLabel); + pdfDoc.setProducer(stirlingPDFLabel); + const pdfBytes = await pdfDoc.save(); const pdfBlob = new Blob([pdfBytes], { type: "application/pdf" }); diff --git a/src/main/resources/static/js/multitool/horizontalScroll.js b/src/main/resources/static/js/multitool/horizontalScroll.js index 2d20fd7b269..28ec42f51bd 100644 --- a/src/main/resources/static/js/multitool/horizontalScroll.js +++ b/src/main/resources/static/js/multitool/horizontalScroll.js @@ -1,27 +1,32 @@ const scrollDivHorizontally = (id) => { - var scrollDelta = 0; // variable to store the accumulated scroll delta + var scrollDeltaX = 0; // variable to store the accumulated horizontal scroll delta + var scrollDeltaY = 0; // variable to store the accumulated vertical scroll delta var isScrolling = false; // variable to track if scroll is already in progress - const divToScrollHorizontally = document.getElementById(id); + const divToScroll = document.getElementById(id); + function scrollLoop() { - // Scroll the div horizontally by a fraction of the accumulated scroll delta - divToScrollHorizontally.scrollLeft += scrollDelta * 0.1; + // Scroll the div horizontally and vertically by a fraction of the accumulated scroll delta + divToScroll.scrollLeft += scrollDeltaX * 0.1; + divToScroll.scrollTop += scrollDeltaY * 0.1; // Reduce the accumulated scroll delta by a fraction - scrollDelta *= 0.9; + scrollDeltaX *= 0.9; + scrollDeltaY *= 0.9; // If scroll delta is still significant, continue the scroll loop - if (Math.abs(scrollDelta) > 0.1) { + if (Math.abs(scrollDeltaX) > 0.1 || Math.abs(scrollDeltaY) > 0.1) { requestAnimationFrame(scrollLoop); } else { isScrolling = false; // Reset scroll in progress flag } } - divToScrollHorizontally.addEventListener("wheel", function (e) { + divToScroll.addEventListener("wheel", function (e) { e.preventDefault(); // prevent default mousewheel behavior - // Accumulate the horizontal scroll delta - scrollDelta -= e.deltaX || e.wheelDeltaX || -e.deltaY || -e.wheelDeltaY; + // Accumulate the horizontal and vertical scroll delta + scrollDeltaX -= e.deltaX || e.wheelDeltaX || -e.deltaY || -e.wheelDeltaY; + scrollDeltaY -= e.deltaY || e.wheelDeltaY || -e.deltaX || -e.wheelDeltaX; // If scroll is not already in progress, start the scroll loop if (!isScrolling) { @@ -31,4 +36,4 @@ const scrollDivHorizontally = (id) => { }); }; -export default scrollDivHorizontally; +export default scrollDivHorizontally; \ No newline at end of file diff --git a/src/main/resources/templates/account.html b/src/main/resources/templates/account.html index a0e54397824..c9d087236b2 100644 --- a/src/main/resources/templates/account.html +++ b/src/main/resources/templates/account.html @@ -278,7 +278,7 @@

Settings Comparison:

let allKeys = new Set([...Object.keys(accountSettings), ...Object.keys(localStorage)]); allKeys.forEach(key => { - if(key === 'debug' || key === '0' || key === '1') return; // Ignoring specific keys + if(key === 'debug' || key === '0' || key === '1' || key.includes('pdfjs') || key.includes('posthog') || key.includes('pageViews')) return; // Ignoring specific keys const accountValue = accountSettings[key] || '-'; const browserValue = localStorage.getItem(key) || '-'; @@ -299,7 +299,7 @@

Settings Comparison:

// Then, set the account settings to local storage for (let key in accountSettings) { - if(key !== 'debug' && key !== '0' && key !== '1') { // Only sync non-ignored keys + if(key !== 'debug' && key !== '0' && key !== '1' && !key.includes('pdfjs') && !key.includes('posthog') && !key.includes('pageViews')) { // Only sync non-ignored keys localStorage.setItem(key, accountSettings[key]); } } @@ -316,7 +316,7 @@

Settings Comparison:

for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); - if(key !== 'debug' && key !== '0' && key !== '1') { // Only send non-ignored keys + if(key !== 'debug' && key !== '0' && key !== '1' && !key.includes('pdfjs') && !key.includes('posthog') && !key.includes('pageViews')) { // Only send non-ignored keys let hiddenField = document.createElement("input"); hiddenField.type = "hidden"; hiddenField.name = key; diff --git a/src/main/resources/templates/addUsers.html b/src/main/resources/templates/addUsers.html index a91f0e46421..18bfb7d28ca 100644 --- a/src/main/resources/templates/addUsers.html +++ b/src/main/resources/templates/addUsers.html @@ -101,6 +101,8 @@ +

+ + + + + @@ -148,6 +191,7 @@ const multipleInputsForSingleRequest = /*[[${multipleInputsForSingleRequest}]]*/ false; const disableMultipleFiles = /*[[${disableMultipleFiles}]]*/ false; const remoteCall = /*[[${remoteCall}]]*/ true; + const sessionExpired = /*[[#{session.expired}]]*/ ''; diff --git a/src/main/resources/templates/fragments/footer.html b/src/main/resources/templates/fragments/footer.html index 40f64419a3f..6cf37fd4a18 100644 --- a/src/main/resources/templates/fragments/footer.html +++ b/src/main/resources/templates/fragments/footer.html @@ -4,7 +4,7 @@
  • Licenses
  • -
  • Survey
  • +
  • Survey
  • privacyPolicy
  • termsAndConditions
  • accessibilityStatement
  • diff --git a/src/main/resources/templates/fragments/navbar.html b/src/main/resources/templates/fragments/navbar.html index fe0c8842d30..24a2ca5714a 100644 --- a/src/main/resources/templates/fragments/navbar.html +++ b/src/main/resources/templates/fragments/navbar.html @@ -229,6 +229,9 @@
    +
    +
    @@ -357,6 +360,12 @@
+ + + ``` + + +## Adding New Translations to Existing Language Files in Stirling-PDF + +When adding a new feature or modifying existing ones in Stirling-PDF, you'll need to add new translation entries to the existing language files. Here's a step-by-step guide: + +### 1. Locate Existing Language Files + +Find the existing `messages.properties` files in the `src/main/resources` directory. You'll see files like: + +- `messages.properties` (default, usually English) +- `messages_en_GB.properties` +- `messages_fr.properties` +- `messages_de.properties` +- etc. + +### 2. Add New Translation Entries + +Open each of these files and add your new translation entries. For example, if you're adding a new feature called "PDF Splitter", +Use descriptive, hierarchical keys (e.g., `feature.element.description`) +you might add: + +```properties +pdfSplitter.title=PDF Splitter +pdfSplitter.description=Split your PDF into multiple documents +pdfSplitter.button.split=Split PDF +pdfSplitter.input.pages=Enter page numbers to split +``` + +Add these entries to the default GB language file and any others you wish, translating the values as appropriate for each language. + +### 3. Use Translations in Thymeleaf Templates + +In your Thymeleaf templates, use the `#{key}` syntax to reference the new translations: + +```html +

PDF Splitter

+

Split your PDF into multiple documents

+ + +``` + + + +Remember, never hard-code text in your templates or Java code. Always use translation keys to ensure proper localization. From 4132e5b78b41b39b277fac4a22cfe683c4d16db7 Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Fri, 18 Oct 2024 06:22:14 +0800 Subject: [PATCH 13/41] Update and improve zh_TW Traditional Chinese locale (#2030) --- src/main/resources/messages_zh_TW.properties | 342 +++++++++---------- 1 file changed, 171 insertions(+), 171 deletions(-) diff --git a/src/main/resources/messages_zh_TW.properties b/src/main/resources/messages_zh_TW.properties index 3f5f50f44f0..57acef0bfa4 100644 --- a/src/main/resources/messages_zh_TW.properties +++ b/src/main/resources/messages_zh_TW.properties @@ -3,8 +3,8 @@ ########### # the direction that the language is written (ltr = left to right, rtl = right to left) language.direction=ltr -addPageNumbers.fontSize=Font Size -addPageNumbers.fontName=Font Name +addPageNumbers.fontSize=字型大小 +addPageNumbers.fontName=字型名稱 pdfPrompt=選擇 PDF 檔案 multiPdfPrompt=選擇多個 PDF 檔案 multiPdfDropPrompt=選擇(或拖放)所有需要的 PDF 檔案 @@ -12,7 +12,7 @@ imgPrompt=選擇圖片 genericSubmit=送出 processTimeWarning=警告:此過程可能長達一分鐘,具體取決於檔案大小 pageOrderPrompt=自訂頁面順序(輸入以逗號分隔的頁碼或函式,如 2n+1): -pageSelectionPrompt=自訂頁面選擇(輸入以逗號分隔的頁碼 1、5、6 或 2n+1 等函數的清單): +pageSelectionPrompt=自訂頁面選擇(輸入以逗號分隔的頁碼 1、5、6 或 2n+1 等函式的清單): goToPage=前往 true=是 false=否 @@ -51,62 +51,62 @@ poweredBy=Powered by yes=是 no=否 changedCredsMessage=憑證已變更! -notAuthenticatedMessage=使用者未認證。 +notAuthenticatedMessage=使用者未通過驗證。 userNotFoundMessage=找不到使用者。 incorrectPasswordMessage=目前密碼不正確。 usernameExistsMessage=新使用者名稱已存在。 -invalidUsernameMessage=使用者名稱無效,使用者名稱只能包含字母、數字和以下特殊字元 @._+- 或必須是有效的電子郵件地址。 -invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end. -confirmPasswordErrorMessage=輸入的密碼必需和確認密碼相同。 -deleteCurrentUserMessage=無法刪除目前登錄的使用者。 -deleteUsernameExistsMessage=使用者名不存在,無法刪除。 +invalidUsernameMessage=無效的使用者名稱。使用者名稱只能包含字母、數字和以下特殊字元 @._+- 或必須是有效的電子郵件地址。 +invalidPasswordMessage=密碼不能為空,且開頭和結尾不能有空格。 +confirmPasswordErrorMessage=新密碼與確認新密碼必須相符。 +deleteCurrentUserMessage=無法刪除目前登入的使用者。 +deleteUsernameExistsMessage=使用者名稱不存在,無法刪除。 downgradeCurrentUserMessage=無法降級目前使用者的角色 -disabledCurrentUserMessage=The current user cannot be disabled -downgradeCurrentUserLongMessage=無法降級目前使用者的角色。因此,不會顯示目前的使用者。 -userAlreadyExistsOAuthMessage=該使用者已於 OAuth2 註冊。 -userAlreadyExistsWebMessage=該使用者已於網頁註冊。 +disabledCurrentUserMessage=無法停用目前使用者 +downgradeCurrentUserLongMessage=無法降級目前使用者的角色。因此,將不會顯示目前使用者。 +userAlreadyExistsOAuthMessage=使用者已經以 OAuth2 使用者身份存在。 +userAlreadyExistsWebMessage=使用者已經以網頁使用者身份存在。 error=錯誤 oops=哎呀! -help=幫助 +help=說明 goHomepage=前往首頁 joinDiscord=加入我們的 Discord 伺服器 -seeDockerHub=查看 Docker Hub -visitGithub=訪問 GitHub 存儲庫 -donate=捐贈 +seeDockerHub=造訪 Docker Hub 儲存庫 +visitGithub=造訪 GitHub 專案 +donate=捐款 color=顏色 sponsor=贊助 -info=Info -pro=Pro -page=Page -pages=Pages +info=資訊 +pro=專業版 +page=頁面 +pages=頁面 -legal.privacy=Privacy Policy -legal.terms=Terms and Conditions -legal.accessibility=Accessibility -legal.cookie=Cookie Policy -legal.impressum=Impressum +legal.privacy=隱私權政策 +legal.terms=使用條款 +legal.accessibility=無障礙性聲明 +legal.cookie=Cookie 政策 +legal.impressum=版本說明 ############### # Pipeline # ############### -pipeline.header=管道選單(測試版) -pipeline.uploadButton=上傳自定義 -pipeline.configureButton=配置 +pipeline.header=管道功能選單(測試版) +pipeline.uploadButton=上傳自訂 +pipeline.configureButton=設定 pipeline.defaultOption=自訂 pipeline.submitButton=送出 -pipeline.help=管道説明 -pipeline.scanHelp=資料夾掃描説明 -pipeline.deletePrompt=確定刪除該管道? +pipeline.help=管道功能說明 +pipeline.scanHelp=資料夾掃描說明 +pipeline.deletePrompt=您確定要刪除此管道嗎? ###################### # Pipeline Options # ###################### -pipelineOptions.header=管道配置 +pipelineOptions.header=管道設定 pipelineOptions.pipelineNameLabel=管道名稱 -pipelineOptions.saveSettings=保存操作設置 -pipelineOptions.pipelineNamePrompt=在此處輸入管道名稱 +pipelineOptions.saveSettings=儲存操作設定 +pipelineOptions.pipelineNamePrompt=請在此輸入管道名稱 pipelineOptions.selectOperation=選擇操作 -pipelineOptions.addOperationButton=添加操作 +pipelineOptions.addOperationButton=新增操作 pipelineOptions.pipelineHeader=管道: pipelineOptions.saveButton=下載 pipelineOptions.validateButton=驗證 @@ -114,21 +114,21 @@ pipelineOptions.validateButton=驗證 ######################## # ENTERPRISE EDITION # ######################## -enterpriseEdition.button=Upgrade to Pro -enterpriseEdition.warning=This feature is only available to Pro users. -enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. -enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro +enterpriseEdition.button=升級至專業版 +enterpriseEdition.warning=此功能僅提供給專業版使用者使用。 +enterpriseEdition.yamlAdvert=Stirling PDF 專業版支援 YAML 設定檔和其他單一登入 (SSO) 功能。 +enterpriseEdition.ssoAdvert=需要更多使用者管理功能嗎?請參考 Stirling PDF 專業版 ################# # Analytics # ################# -analytics.title=Do you want make Stirling PDF better? -analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. -analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. -analytics.enable=Enable analytics -analytics.disable=Disable analytics -analytics.settings=You can change the settings for analytics in the config/settings.yml file +analytics.title=您想協助改善 Stirling PDF 嗎? +analytics.paragraph1=Stirling PDF 有選擇性的分析功能,可幫助我們改進產品。我們不會追蹤任何個人資訊或檔案內容。 +analytics.paragraph2=請考慮啟用分析功能,以協助 Stirling-PDF 成長並讓我們更了解使用者需求。 +analytics.enable=啟用分析功能 +analytics.disable=停用分析功能 +analytics.settings=您可以在 config/settings.yml 檔案中變更分析功能的設定 ############# # NAVBAR # @@ -138,36 +138,36 @@ navbar.darkmode=深色模式 navbar.language=語言 navbar.settings=設定 navbar.allTools=工具 -navbar.multiTool=萬用工具 -navbar.sections.organize=組織 +navbar.multiTool=多功能工具 +navbar.sections.organize=整理 navbar.sections.convertTo=轉換為 PDF navbar.sections.convertFrom=從 PDF 轉換 navbar.sections.security=簽章與安全性 navbar.sections.advance=進階 navbar.sections.edit=檢視與編輯 -navbar.sections.popular=Popular +navbar.sections.popular=熱門功能 ############# # SETTINGS # ############# settings.title=設定 settings.update=有更新可用 -settings.updateAvailable=當前版本為 {0}。歡迎你更新至最新版 ({1})。。 -settings.appVersion=應用版本: -settings.downloadOption.title=選擇下載選項(對於單一檔案非壓縮下載): +settings.updateAvailable=目前安裝的版本是 {0}。有新版本({1})可供使用。 +settings.appVersion=應用程式版本: +settings.downloadOption.title=選擇下載選項(適用於單一檔案非壓縮下載): settings.downloadOption.1=在同一視窗中開啟 settings.downloadOption.2=在新視窗中開啟 settings.downloadOption.3=下載檔案 -settings.zipThreshold=當下載的檔案數量超過時,壓縮檔案 +settings.zipThreshold=當下載檔案數量超過此數值時,將檔案壓縮 settings.signOut=登出 -settings.accountSettings=帳戶設定 -settings.bored.help=啟用彩蛋 -settings.cacheInputs.name=輸入檔案下載 -settings.cacheInputs.help=開啟記住先前的輸入,做為日後使用 +settings.accountSettings=帳號設定 +settings.bored.help=啟用彩蛋遊戲 +settings.cacheInputs.name=儲存表單輸入 +settings.cacheInputs.help=啟用此功能以儲存先前使用的輸入,以便日後使用 changeCreds.title=變更憑證 -changeCreds.header=更新你的帳戶詳細資訊 -changeCreds.changePassword=你使用的是預設登錄認證。請輸入新密碼 +changeCreds.header=更新您的帳號詳細資訊 +changeCreds.changePassword=您正在使用預設登入憑證。請輸入新密碼 changeCreds.newUsername=新使用者名稱 changeCreds.oldPassword=目前密碼 changeCreds.newPassword=新密碼 @@ -176,9 +176,9 @@ changeCreds.submit=送出變更 -account.title=帳戶設定 -account.accountSettings=帳戶設定 -account.adminSettings=管理設定 - 檢視和新增使用者 +account.title=帳號設定 +account.accountSettings=帳號設定 +account.adminSettings=管理員設定 - 檢視和新增使用者 account.userControlSettings=使用者控制設定 account.changeUsername=修改使用者名稱 account.newUsername=新使用者名稱 @@ -188,43 +188,43 @@ account.newPassword=新密碼 account.changePassword=修改密碼 account.confirmNewPassword=確認新密碼 account.signOut=登出 -account.yourApiKey=你的 API 金鑰 -account.syncTitle=將瀏覽器設定與帳戶同步 +account.yourApiKey=您的 API 金鑰 +account.syncTitle=將瀏覽器設定與帳號同步 account.settingsCompare=設定比較: account.property=屬性 account.webBrowserSettings=網頁瀏覽器設定 -account.syncToBrowser=同步帳戶 → 瀏覽器 -account.syncToAccount=同步帳戶 ← 瀏覽器 +account.syncToBrowser=同步帳號 → 瀏覽器 +account.syncToAccount=同步帳號 ← 瀏覽器 adminUserSettings.title=使用者控制設定 -adminUserSettings.header=管理使用者控制設定 +adminUserSettings.header=管理員使用者控制設定 adminUserSettings.admin=管理員 adminUserSettings.user=使用者 adminUserSettings.addUser=新增使用者 adminUserSettings.deleteUser=刪除使用者 -adminUserSettings.confirmDeleteUser=確認刪除該使用者? -adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled? -adminUserSettings.usernameInfo=使用者名稱只能包含字母、數字和以下特殊字元 @._+-,或必須是有效的電子郵件地址。 +adminUserSettings.confirmDeleteUser=確定要刪除此使用者? +adminUserSettings.confirmChangeUserStatus=是否要停用/啟用此使用者? +adminUserSettings.usernameInfo=使用者名稱只能包含字母、數字和以下特殊字元 @._+- 或必須是有效的電子郵件地址。 adminUserSettings.roles=角色 adminUserSettings.role=角色 adminUserSettings.actions=操作 adminUserSettings.apiUser=受限制的 API 使用者 -adminUserSettings.extraApiUser=其他受限 API 使用者 -adminUserSettings.webOnlyUser=僅使用網頁的使用者 -adminUserSettings.demoUser=示範用途的使用者(無自訂設定) +adminUserSettings.extraApiUser=額外受限制的 API 使用者 +adminUserSettings.webOnlyUser=僅網頁版使用者 +adminUserSettings.demoUser=示範使用者(無自訂設定) adminUserSettings.internalApiUser=內部 API 使用者 -adminUserSettings.forceChange=強制使用者在登入時修改使用者名稱/密碼 -adminUserSettings.submit=儲存 -adminUserSettings.changeUserRole=更改使用者身份 +adminUserSettings.forceChange=強制使用者在登入時變更密碼 +adminUserSettings.submit=儲存使用者 +adminUserSettings.changeUserRole=變更使用者角色 adminUserSettings.authenticated=已驗證 -adminUserSettings.editOwnProfil=Edit own profile -adminUserSettings.enabledUser=enabled user -adminUserSettings.disabledUser=disabled user -adminUserSettings.activeUsers=Active Users: -adminUserSettings.disabledUsers=Disabled Users: -adminUserSettings.totalUsers=Total Users: -adminUserSettings.lastRequest=Last Request +adminUserSettings.editOwnProfil=編輯自己的個人資料 +adminUserSettings.enabledUser=已啟用使用者 +adminUserSettings.disabledUser=已停用使用者 +adminUserSettings.activeUsers=使用中的使用者: +adminUserSettings.disabledUsers=已停用的使用者: +adminUserSettings.totalUsers=使用者總數: +adminUserSettings.lastRequest=最後請求時間 database.title=資料庫匯入/匯出 @@ -235,20 +235,20 @@ database.fileSize=檔案大小 database.deleteBackupFile=刪除備份檔案 database.importBackupFile=匯入備份檔案 database.downloadBackupFile=下載備份檔案 -database.info_1=在匯入資料時,確保正確的結構是至關重要的。如果你不確定自己在做什麼,請尋求專業人士的建議和支持。結構錯誤可能會導致應用程式故障,甚至完全無法運行。 -database.info_2=上傳時檔案名稱無關緊要。上傳後將重新命名為 backup_user_yyyyMMddHHmm.sql 格式,以確保命名規範一致。 +database.info_1=在匯入資料時,確保正確的結構至關重要。如果您不確定自己在做什麼,請尋求專業人士的建議和支援。結構錯誤可能會導致應用程式故障,甚至完全無法執行應用程式。 +database.info_2=上傳時檔案名稱並不重要。上傳後將重新命名為 backup_user_yyyyMMddHHmm.sql 格式,以確保命名規範一致。 database.submit=匯入備份 database.importIntoDatabaseSuccessed=成功匯入資料庫 -database.fileNotFound=檔案未找到 -database.fileNullOrEmpty=檔案不能為空或空白 +database.fileNotFound=找不到檔案 +database.fileNullOrEmpty=檔案不得為空或空白 database.failedImportFile=匯入檔案失敗 -session.expired=Your session has expired. Please refresh the page and try again. +session.expired=您的工作階段已過期。請重新整理頁面並再試一次。 ############# # HOME-PAGE # ############# -home.desc=你的本地主機一站式 PDF 需求解決方案。 +home.desc=你的本機主機一站式 PDF 需求解決方案。 home.searchBar=搜尋功能... @@ -392,7 +392,7 @@ certSign.tags=驗證,PEM,P12,官方,加密 home.removeCertSign.title=移除簽章 home.removeCertSign.desc=從 PDF 移除簽章 -removeCertSign.tags=authenticate,PEM,P12,official,decrypt +removeCertSign.tags=驗證,PEM,P12,官方,解密 home.pageLayout.title=多頁面版面配置 home.pageLayout.desc=將 PDF 檔案的多個頁面合併到單一頁面 @@ -485,9 +485,9 @@ home.split-by-sections.title=依區段分割 PDF home.split-by-sections.desc=將 PDF 的每一頁分割為較小的水平和垂直區段 split-by-sections.tags=區段分割, 劃分, 自訂 -home.AddStampRequest.title=將圖章添加到 PDF -home.AddStampRequest.desc=在設置位置添加文字或添加圖像圖章 -AddStampRequest.tags=圖章,添加圖片,中心圖像,浮水印,PDF,嵌入,自訂 +home.AddStampRequest.title=將圖章新增到 PDF +home.AddStampRequest.desc=在設定位置新增文字或新增影像圖章 +AddStampRequest.tags=圖章,新增圖片,中心影像,浮水印,PDF,嵌入,自訂 home.PDFToBook.title=PDF 轉電子書 @@ -498,33 +498,33 @@ home.BookToPDF.title=電子書轉 PDF home.BookToPDF.desc=使用 calibre 將書籍/漫畫格式轉換為 PDF BookToPDF.tags=電子書,漫畫,Calibre,轉換,日本漫畫,亞馬遜,kindle -home.removeImagePdf.title=Remove image -home.removeImagePdf.desc=Remove image from PDF to reduce file size -removeImagePdf.tags=Remove Image,Page operations,Back end,server side +home.removeImagePdf.title=移除圖片 +home.removeImagePdf.desc=從 PDF 中移除圖片以減少檔案大小 +removeImagePdf.tags=移除圖片,頁面操作,後端,伺服器端 -home.splitPdfByChapters.title=Split PDF by Chapters -home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. -splitPdfByChapters.tags=split,chapters,bookmarks,organize +home.splitPdfByChapters.title=依章節分割 PDF +home.splitPdfByChapters.desc=根據 PDF 的章節結構將其分割成多個檔案。 +splitPdfByChapters.tags=分割,章節,書籤,整理 #replace-invert-color -replace-color.title=Replace-Invert-Color -replace-color.header=Replace-Invert Color PDF -home.replaceColorPdf.title=Replace and Invert Color -home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size -replaceColorPdf.tags=Replace Color,Page operations,Back end,server side -replace-color.selectText.1=Replace or Invert color Options -replace-color.selectText.2=Default(Default high contrast colors) -replace-color.selectText.3=Custom(Customized colors) -replace-color.selectText.4=Full-Invert(Invert all colors) -replace-color.selectText.5=High contrast color options -replace-color.selectText.6=white text on black background -replace-color.selectText.7=Black text on white background -replace-color.selectText.8=Yellow text on black background -replace-color.selectText.9=Green text on black background -replace-color.selectText.10=Choose text Color -replace-color.selectText.11=Choose background Color -replace-color.submit=Replace +replace-color.title=取代-反轉顏色 +replace-color.header=取代-反轉 PDF 顏色 +home.replaceColorPdf.title=取代與反轉顏色 +home.replaceColorPdf.desc=取代 PDF 中文字和背景的顏色,並反轉整個 PDF 的顏色以減少檔案大小 +replaceColorPdf.tags=取代顏色,頁面操作,後端,伺服器端 +replace-color.selectText.1=取代或反轉顏色選項 +replace-color.selectText.2=預設(預設高對比度顏色) +replace-color.selectText.3=自訂(自訂顏色) +replace-color.selectText.4=全部反轉(反轉所有顏色) +replace-color.selectText.5=高對比度顏色選項 +replace-color.selectText.6=黑底白字 +replace-color.selectText.7=白底黑字 +replace-color.selectText.8=黑底黃字 +replace-color.selectText.9=黑底綠字 +replace-color.selectText.10=選擇文字顏色 +replace-color.selectText.11=選擇背景顏色 +replace-color.submit=取代 @@ -542,19 +542,19 @@ login.invalid=使用者名稱或密碼無效。 login.locked=你的帳戶已被鎖定。 login.signinTitle=請登入 login.ssoSignIn=透過織網單一簽入 -login.oauth2AutoCreateDisabled=OAuth 2.0 自動建立使用者已停用 -login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator. +login.oauth2AutoCreateDisabled=OAuth 2.0 自動建立使用者功能已停用 +login.oauth2AdminBlockedUser=目前已封鎖非註冊使用者的註冊或登入。請聯絡系統管理員。 login.oauth2RequestNotFound=找不到驗證請求 login.oauth2InvalidUserInfoResponse=無效的使用者資訊回應 login.oauth2invalidRequest=無效的回應 login.oauth2AccessDenied=存取被拒 -login.oauth2InvalidTokenResponse=無效的 Token 回應 -login.oauth2InvalidIdToken=無效的 Tokne -login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator. -login.alreadyLoggedIn=You are already logged in to -login.alreadyLoggedIn2=devices. Please log out of the devices and try again. -login.toManySessions=You have too many active sessions -login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. +login.oauth2InvalidTokenResponse=無效的權杖回應 +login.oauth2InvalidIdToken=無效的識別權杖 +login.userIsDisabled=使用者已停用,目前此使用者無法登入。請聯絡系統管理員。 +login.alreadyLoggedIn=您已經登入了 +login.alreadyLoggedIn2=個裝置。請登出其他裝置後再試一次。 +login.toManySessions=您有太多使用中的工作階段 +login.toManySessions2=請登出其他裝置後再試一次。或者,您可以升級至 Stirling PDF 專業版。 #auto-redact autoRedact.title=自動塗黑 @@ -640,7 +640,7 @@ AddStampRequest.stampType=圖章類型 AddStampRequest.stampText=圖章文字 AddStampRequest.stampImage=圖章圖片 AddStampRequest.alphabet=字母表 -AddStampRequest.fontSize=字體/圖像大小 +AddStampRequest.fontSize=字型/影像大小 AddStampRequest.rotation=旋轉 AddStampRequest.opacity=透明度 AddStampRequest.position=位置 @@ -729,7 +729,7 @@ pageLayout.submit=送出 scalePages.title=調整頁面大小/比例 scalePages.header=調整頁面大小/比例 scalePages.pageSize=文件的頁面大小。 -scalePages.keepPageSize=Original Size +scalePages.keepPageSize=原始大小 scalePages.scaleFactor=頁面的縮放級別(裁剪)。 scalePages.submit=送出 @@ -738,7 +738,7 @@ scalePages.submit=送出 certSign.title=憑證簽章 certSign.header=使用你的憑證簽章(進行中) certSign.selectPDF=選擇要簽章的 PDF 檔案: -certSign.jksNote=注意:如果你的證書類型未在下面列出,請使用 keytool 命令行工具將其轉換為 Java Keystore (.jks) 檔。 然後,選擇下面的 .jks 文件選項。 +certSign.jksNote=注意:如果你的證書類型未在下面列出,請使用 keytool 命令列工具將其轉換為 Java Keystore (.jks) 檔。 然後,選擇下面的 .jks 文件選項。 certSign.selectKey=選擇你的私鑰文件(PKCS#8 格式,可能是 .pem 或 .der): certSign.selectCert=選擇你的憑證文件(X.509 格式,可能是 .pem 或 .der): certSign.selectP12=選擇你的 PKCS#12 金鑰庫文件(.p12 或 .pfx)(可選,如果提供,它應包含你的私鑰和憑證): @@ -778,8 +778,8 @@ removeAnnotations.submit=移除 #compare compare.title=比較 compare.header=比較 PDF -compare.highlightColor.1=Highlight Color 1: -compare.highlightColor.2=Highlight Color 2: +compare.highlightColor.1=標示顏色 1: +compare.highlightColor.2=標示顏色 2: compare.document.1=文件 1 compare.document.2=文件 2 compare.submit=比較 @@ -816,7 +816,7 @@ repair.submit=修復 #flatten flatten.title=平坦化 flatten.header=PDF 平坦化 -flatten.flattenOnlyForms=Flatten only forms +flatten.flattenOnlyForms=僅將表單平坦化 flatten.submit=平坦化 @@ -831,7 +831,7 @@ ScannerImageSplit.selectText.7=最小輪廓區域: ScannerImageSplit.selectText.8=設定照片的最小輪廓區域閾值 ScannerImageSplit.selectText.9=邊框大小: ScannerImageSplit.selectText.10=設定新增和移除的邊框大小,以防止輸出中的白色邊框(預設:1)。 -ScannerImageSplit.info=Python is not installed. It is required to run. +ScannerImageSplit.info=尚未安裝 Python。需要安裝 Python 才能執行。 #OCR @@ -914,14 +914,14 @@ pdfOrganiser.mode.6=奇偶拆分 pdfOrganiser.mode.7=刪除第一頁 pdfOrganiser.mode.8=刪除最後一頁 pdfOrganiser.mode.9=刪除第一頁和最後一頁 -pdfOrganiser.mode.10=Odd-Even Merge +pdfOrganiser.mode.10=奇偶合併 pdfOrganiser.placeholder=(例如 1,3,2 或 4-8,2,10-12 或 2n-1) #multiTool multiTool.title=PDF 多工具 multiTool.header=PDF 多工具 -multiTool.uploadPrompts=File Name +multiTool.uploadPrompts=檔名 #view pdf viewPdf.title=檢視 PDF @@ -983,7 +983,7 @@ pdfToImage.color=顏色 pdfToImage.grey=灰度 pdfToImage.blackwhite=黑白(可能會遺失資料!) pdfToImage.submit=轉換 -pdfToImage.info=Python is not installed. Required for WebP conversion. +pdfToImage.info=尚未安裝 Python。需要安裝 Python 才能進行 WebP 轉換。 #addPassword @@ -1020,7 +1020,7 @@ watermark.selectText.6=heightSpacer(每個浮水印之間的垂直間距): watermark.selectText.7=不透明度(0% - 100%): watermark.selectText.8=浮水印類型: watermark.selectText.9=浮水印影像: -watermark.selectText.10=Convert PDF to PDF-Image +watermark.selectText.10=將 PDF 轉換為 PDF 影像 watermark.submit=新增浮水印 watermark.type.1=文字 watermark.type.2=圖片 @@ -1171,55 +1171,55 @@ printFile.submit=列印 #licenses -licenses.nav=許可證 -licenses.title=第三方許可證 -licenses.header=第三方許可證 +licenses.nav=授權條款 +licenses.title=第三方授權條款 +licenses.header=第三方授權條款 licenses.module=模組 licenses.version=版本 -licenses.license=許可證 +licenses.license=授權條款 #survey survey.nav=問卷調查 survey.title=Stirling-PDF 問卷調查 -survey.description=Stirling-PDF 沒有追蹤功能,所以我們希望聽取用戶的意見來改進 Stirling-PDF! -survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: -survey.changes2=With these changes we are getting paid business support and funding -survey.please=請考慮參加我們的問卷調查! -survey.disabled=(問卷調查彈出窗口將在後續更新中停用,但仍可在頁腳處使用) -survey.button=參加問卷調查 +survey.description=Stirling-PDF 沒有追蹤功能,因此我們希望聽取使用者的意見來改進 Stirling-PDF! +survey.changes=Stirling-PDF 自上次調查以來已有所改變!欲了解更多資訊,請查看我們的部落格文章: +survey.changes2=隨著這些變更,我們正在獲得付費的商業支援和資金 +survey.please=請考慮參與我們的問卷調查! +survey.disabled=(問卷調查彈出視窗將在後續更新中停用,但仍可在頁尾使用) +survey.button=參與問卷調查 survey.dontShowAgain=不要再次顯示 #error -error.sorry=對於這個問題,我們感到抱歉! -error.needHelp=需要幫助/發現了一個問題? -error.contactTip=如果你仍然遇到問題,請不要猶豫,隨時向我們尋求幫助。你可以在我們的 GitHub 頁面提交工單,或通過 Discord 與我們聯繋: -error.404.head=404 - 找不到頁面 | 哎呀,我們在代碼中走錯了路! -error.404.1=我們好像找不到你正在尋找的頁面。 -error.404.2=出了點錯誤 -error.github=在 GitHub 上提交工單 +error.sorry=很抱歉造成您的困擾! +error.needHelp=需要協助或發現問題? +error.contactTip=如果您仍然遇到問題,請不要猶豫,隨時向我們尋求協助。您可以在我們的 GitHub 頁面回報問題,或透過 Discord 跟我們聯絡: +error.404.head=404 - 找不到頁面 | 糟糕,我們在程式碼中迷路了! +error.404.1=我們似乎找不到您正在尋找的頁面。 +error.404.2=發生了一些錯誤 +error.github=在 GitHub 上回報問題 error.showStack=顯示堆疊追蹤 error.copyStack=複製堆疊追蹤 -error.githubSubmit=GitHub - 提交工單 -error.discordSubmit=Discord - 提交支援帖子 +error.githubSubmit=GitHub - 回報問題 +error.discordSubmit=Discord - 發表支援文章 #remove-image -removeImage.title=Remove image -removeImage.header=Remove image -removeImage.removeImage=Remove image -removeImage.submit=Remove image - - -splitByChapters.title=Split PDF by Chapters -splitByChapters.header=Split PDF by Chapters -splitByChapters.bookmarkLevel=Bookmark Level -splitByChapters.includeMetadata=Include Metadata -splitByChapters.allowDuplicates=Allow Duplicates -splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. -splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). -splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. -splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. -splitByChapters.submit=Split PDF +removeImage.title=移除圖片 +removeImage.header=移除圖片 +removeImage.removeImage=移除圖片 +removeImage.submit=移除圖片 + + +splitByChapters.title=依章節分割 PDF +splitByChapters.header=依章節分割 PDF +splitByChapters.bookmarkLevel=書籤層級 +splitByChapters.includeMetadata=包含中繼資料 +splitByChapters.allowDuplicates=允許重複 +splitByChapters.desc.1=此工具會根據 PDF 檔案的章節結構將其分割成多個 PDF。 +splitByChapters.desc.2=書籤層級:選擇用於分割的書籤層級(0 表示最上層,1 表示第二層,依此類推)。 +splitByChapters.desc.3=包含中繼資料:如果勾選,原始 PDF 的中繼資料將包含在每個分割後的 PDF 中。 +splitByChapters.desc.4=允許重複:如果勾選,允許同一頁面上的多個書籤建立獨立的 PDF。 +splitByChapters.submit=分割 PDF From 80d80f7d8f1f9f0146b300d6964b3e047ea04e41 Mon Sep 17 00:00:00 2001 From: thiagoor-cpu Date: Thu, 17 Oct 2024 19:42:47 -0300 Subject: [PATCH 14/41] Update messages_pt_BR.properties (#2029) Several changes to pt_BR version --- src/main/resources/messages_pt_BR.properties | 358 +++++++++---------- 1 file changed, 179 insertions(+), 179 deletions(-) diff --git a/src/main/resources/messages_pt_BR.properties b/src/main/resources/messages_pt_BR.properties index 1c809b8a298..6c263ad8f7b 100644 --- a/src/main/resources/messages_pt_BR.properties +++ b/src/main/resources/messages_pt_BR.properties @@ -3,42 +3,42 @@ ########### # the direction that the language is written (ltr = left to right, rtl = right to left) language.direction=ltr -addPageNumbers.fontSize=Tamanho da fonte -addPageNumbers.fontName=Nome da fonte +addPageNumbers.fontSize=Tamanho da Fonte +addPageNumbers.fontName=Nome da Fonte pdfPrompt=Selecione PDF(s) multiPdfPrompt=Selecione PDFs (2+) -multiPdfDropPrompt=Selecione (ou arraste e solte) todos os PDFs necessários -imgPrompt=Selecione a(s) imagem(ns) +multiPdfDropPrompt=Selecione (ou arraste e solte) todos os PDFs desejados +imgPrompt=Selecione a(s) Imagem(ns) genericSubmit=Enviar processTimeWarning=Aviso: esse processo pode levar até um minuto, dependendo do tamanho do arquivo -pageOrderPrompt=Ordem de página personalizada (digite uma lista de números de página separada por vírgula): -pageSelectionPrompt=Seleção de página personalizada (digite uma lista de números de página separada por vírgula 1,5,6 ou Funções como 2n+1) : +pageOrderPrompt=Ordem de Página Personalizada (Digite uma lista de números de páginas, separadas por vírgula ou Funções como 2n+1): +pageSelectionPrompt=Seleção de Página Personalizada (Digite uma lista de números de páginas, separadas por vírgula 1,5,6 ou Funções como 2n+1) : goToPage=Ir true=Verdadeiro false=Falso unknown=Desconhecido save=Salvar -saveToBrowser=Salvar no navegador +saveToBrowser=Salvar no Navegador close=Fechar -filesSelected=arquivos selecionados -noFavourites=Nenhum favorito adicionado +filesSelected=Arquivos Selecionados +noFavourites=Nenhum Favorito Adicionado downloadComplete=Download Completo -bored=Entediado esperando? +bored=Entediado Esperando? alphabet=Alfabeto downloadPdf=Baixar PDF text=Texto font=Fonte selectFillter=-- Selecione -- -pageNum=Número da página +pageNum=Número da Página sizes.small=Pequeno sizes.medium=Médio sizes.large=Grande sizes.x-large=Extra grande error.pdfPassword=O documento PDF está protegido por senha e a senha não foi fornecida ou está incorreta -delete=apagar +delete=Apagar username=Usuário password=Senha -welcome=Bem vindo +welcome=Bem-vindo property=Propriedade black=Preto white=Branco @@ -46,7 +46,7 @@ red=Vermelho green=Verde blue=Azul custom=Personalizado... -WorkInProgess=Em progesso, Talvez não funcione ou apresente erros, Por favor, reporte qualquer problema! +WorkInProgess=Trabalho em progresso, Talvez não funcione ou apresente erros, Por favor, reporte qualquer problema! poweredBy=Distribuído por yes=Sim no=Não @@ -55,30 +55,30 @@ notAuthenticatedMessage=Usuário não autenticado. userNotFoundMessage=Usuário não encontrado. incorrectPasswordMessage=A senha atual está incorreta. usernameExistsMessage=Novo Usuário já existe. -invalidUsernameMessage=Usuário inválido, nome de usuário só pode incluir letras, números e os seguintes caracteres especiais @._+- ou deve ser um email válido. +invalidUsernameMessage=Usuário inválido, nome de usuário só pode incluir letras, números e os seguintes caracteres especiais @._+- ou deve ser um e-mail válido. invalidPasswordMessage=A senha não deve estar vazia e não deve conter espaços no início ou no final. confirmPasswordErrorMessage=Nova Senha e Confirmar Nova Senha devem ser iguais. -deleteCurrentUserMessage=Não é possível apagar o usuário da sessão atual. +deleteCurrentUserMessage=Não é possível apagar usuário conectado no momento. deleteUsernameExistsMessage=O usuário não existe e não pode ser apagado. -downgradeCurrentUserMessage=Não é possível fazer downgrade da função do usuário atual -disabledCurrentUserMessage=O usuário atual não pode ser desativado +downgradeCurrentUserMessage=Não é possível fazer downgrade da função do usuário conectado no momento. +disabledCurrentUserMessage=O usuário atual não pode ser desativado. downgradeCurrentUserLongMessage=Não é possível fazer downgrade da função do usuário atual. Portanto, o usuário atual não será mostrado. userAlreadyExistsOAuthMessage=O usuário já existe como um usuário OAuth2. -userAlreadyExistsWebMessage=O usuário já existe como um usuário web. +userAlreadyExistsWebMessage=O usuário já existe como um usuário Web. error=Erro oops=Ops! help=Ajuda goHomepage=Ir para a Página Inicial joinDiscord=Junte-se ao nosso servidor Discord seeDockerHub=Visite o Docker Hub -visitGithub=Visite o repositŕio no GitHub +visitGithub=Visite o repositório no GitHub donate=Doar color=Cor -sponsor=Patrocine +sponsor=Patrocinador info=Informações pro=Pro -page=Page -pages=Pages +page=Página +pages=Páginas legal.privacy=Política de Privacidade legal.terms=Termos e Condições @@ -90,7 +90,7 @@ legal.impressum=Informações legais # Pipeline # ############### pipeline.header=Menu do Pipeline (Beta) -pipeline.uploadButton=Upload personalizado +pipeline.uploadButton=Upload Personalizado pipeline.configureButton=Configurar pipeline.defaultOption=Personalizado pipeline.submitButton=Enviar @@ -103,10 +103,10 @@ pipeline.deletePrompt=Tem certeza de que deseja excluir o pipeline ###################### pipelineOptions.header=Configuração do Pipeline pipelineOptions.pipelineNameLabel=Nome do Pipeline -pipelineOptions.saveSettings=Salvar configurações da operação +pipelineOptions.saveSettings=Salvar Configurações da Operação pipelineOptions.pipelineNamePrompt=Insira o nome do pipeline aqui -pipelineOptions.selectOperation=Selecione uma operação -pipelineOptions.addOperationButton=Adicione uma operação +pipelineOptions.selectOperation=Selecione uma Operação +pipelineOptions.addOperationButton=Adicione uma Operação pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.saveButton=Baixar pipelineOptions.validateButton=Validar @@ -114,21 +114,21 @@ pipelineOptions.validateButton=Validar ######################## # ENTERPRISE EDITION # ######################## -enterpriseEdition.button=Upgrade to Pro -enterpriseEdition.warning=This feature is only available to Pro users. -enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. -enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro +enterpriseEdition.button=Atualize para versão Pro +enterpriseEdition.warning=Esse recurso só está disponivel para usuários da versão Pro. +enterpriseEdition.yamlAdvert=Stirling PDF Pro suporta arquivos de configuração YAML e outros recursos SSO. +enterpriseEdition.ssoAdvert=Procurando por mais recursos de controle de usuários? Veja Stirling PDF Pro ################# # Analytics # ################# -analytics.title=Do you want make Stirling PDF better? -analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. -analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. -analytics.enable=Enable analytics -analytics.disable=Disable analytics -analytics.settings=You can change the settings for analytics in the config/settings.yml file +analytics.title=Você quer melhorar Stirling PDF? +analytics.paragraph1=Stirling PDF possui coleta de dados opcional para ajudar a melhorar o produto. Nós não rastreamos nenhuma informação pessoal ou conteúdo dos arquivos. +analytics.paragraph2=Por favor considere habilitar coleta de dados para ajudar Stirling-PDF a crescer e nos ajudar a entender nossos usuários melhor. +analytics.enable=Habilitar coleta de dados +analytics.disable=Desabilitar coleta de dados +analytics.settings=Você pode alterar as configurações de coleta de dados no arquivo config/settings.yml ############# # NAVBAR # @@ -162,61 +162,61 @@ settings.zipThreshold=Compactar arquivos quando o número de arquivos baixados e settings.signOut=Sair settings.accountSettings=Configurações de conta settings.bored.help=Habilitar jogos secretos -settings.cacheInputs.name=Salvar entradas de formulário +settings.cacheInputs.name=Salvar entradas do formulário settings.cacheInputs.help=Habilitar para armazenar entradas usadas anteriormente para execuções futuras -changeCreds.title=Alterar credenciais -changeCreds.header=Atualizar detalhes da sua conta +changeCreds.title=Alterar Credenciais +changeCreds.header=Atualizar Detalhes da Conta changeCreds.changePassword=Você está usando as credenciais padrões. Por favor, insira uma nova senha -changeCreds.newUsername=Novo usuário -changeCreds.oldPassword=Senha atual -changeCreds.newPassword=Nova senha -changeCreds.confirmNewPassword=Confirmar nova senha -changeCreds.submit=Enviar alterações - - - -account.title=Configurações de conta -account.accountSettings=Configurações de conta -account.adminSettings=Configurações de Administrador – visualizar e adicionar usuários -account.userControlSettings=Configurações de controle de usuário -account.changeUsername=Alterar usuário -account.newUsername=Novo usuário -account.password=Senha de confirmação -account.oldPassword=Senha antiga -account.newPassword=Senha nova -account.changePassword=Alterar a senha -account.confirmNewPassword=Confirme a nova senha +changeCreds.newUsername=Novo Usuário +changeCreds.oldPassword=Senha Atual +changeCreds.newPassword=Senha Nova +changeCreds.confirmNewPassword=Confirme a Nova Senha +changeCreds.submit=Enviar Alterações + + + +account.title=Configurações de Conta +account.accountSettings=Configurações de Conta +account.adminSettings=Configurações de Administrador – Visualizar e Adicionar Usuários +account.userControlSettings=Configurações de Controle de Usuário +account.changeUsername=Alterar Usuário +account.newUsername=Novo Usuário +account.password=Senha de Confirmação +account.oldPassword=Senha Antiga +account.newPassword=Senha Nova +account.changePassword=Alterar a Senha +account.confirmNewPassword=Confirme a Nova Senha account.signOut=Sair account.yourApiKey=Sua chave de API account.syncTitle=Sincronize as configurações do navegador com a conta -account.settingsCompare=Comparação de configurações: +account.settingsCompare=Comparação de Configurações: account.property=Propriedade account.webBrowserSettings=Configuração do navegador Web -account.syncToBrowser=Sincronizar conta -> Navegador -account.syncToAccount=Sincronizar conta <- navegador +account.syncToBrowser=Sincronizar Conta -> Navegador +account.syncToAccount=Sincronizar Conta <- Navegador adminUserSettings.title=Configurações de controle de usuário -adminUserSettings.header=Configurações de controle de usuário administrador +adminUserSettings.header=Configurações de controle do usuário administrador adminUserSettings.admin=Administrador adminUserSettings.user=Usuário adminUserSettings.addUser=Adicionar novo usuário adminUserSettings.deleteUser=Apagar usuário adminUserSettings.confirmDeleteUser=O usuário deve ser apagado? adminUserSettings.confirmChangeUserStatus=O usuário deve ser desabilitado/habilitado? -adminUserSettings.usernameInfo=Nome de usuário só pode incluir letras, números e os seguintes caracteres especiais @._+- ou deve ser um email válido. +adminUserSettings.usernameInfo=Nome de usuário só pode incluir letras, números e os seguintes caracteres especiais @._+- ou deve ser um e-mail válido. adminUserSettings.roles=Funções adminUserSettings.role=Função adminUserSettings.actions=Ações adminUserSettings.apiUser=Usuário de API limitado adminUserSettings.extraApiUser=Usuário de API limitado adicional -adminUserSettings.webOnlyUser=Usuário apenas web +adminUserSettings.webOnlyUser=Usuário web apenas adminUserSettings.demoUser=Usuário demo (Sem configurações personalizadas) adminUserSettings.internalApiUser=Usuário interno de API adminUserSettings.forceChange=Forçar usuário a trocar a senha ao iniciar sessão -adminUserSettings.submit=Salvar usuário -adminUserSettings.changeUserRole=Alterar Função de Usuário +adminUserSettings.submit=Salvar Usuário +adminUserSettings.changeUserRole=Alterar Função do Usuário adminUserSettings.authenticated=Autenticado adminUserSettings.editOwnProfil=Editar próprio perfil adminUserSettings.enabledUser=usuário habilitado @@ -235,15 +235,15 @@ database.fileSize=Tamanho do arquivo database.deleteBackupFile=Apagar arquivo de backup database.importBackupFile=Importar arquivo de backup database.downloadBackupFile=Baixar arquivo de backup -database.info_1=Ao importar dados, é crucial garantir a estrutura correta. Se você não tem certeza do que está fazendo procure auxílio de um profissional. Um erro na estrutura pode ocasionar em mau funcionamento da aplicação, incluindo a possibilidade de perda total da aplicação. +database.info_1=Ao importar dados, é crucial garantir a estrutura correta. Se você não tem certeza do que está fazendo procure auxílio de um profissional. Um erro na estrutura pode ocasionar em mau funcionamento da aplicação, incluindo a impossibilidade da aplicação ser executada. database.info_2=O nome do arquivo não importa ao enviar. Ele será renomeado em seguida para seguir o formato backup_user_yyyyMMddHHmm.sql, garantindo uma convenção de nomes coerente. database.submit=Importar Backup database.importIntoDatabaseSuccessed=Importação para o banco de dados bem sucedida database.fileNotFound=Arquivo não encontrado -database.fileNullOrEmpty=O arquivo não estar nulo ou vazio +database.fileNullOrEmpty=O arquivo não pode estar nulo ou vazio database.failedImportFile=Falha ao importar arquivo -session.expired=Your session has expired. Please refresh the page and try again. +session.expired=Sua sessão expirou. Por gentileza atualize a página e tente novamente. ############# # HOME-PAGE # @@ -257,11 +257,11 @@ home.viewPdf.desc=Visualizar, anotar, adicionar texto ou imagens viewPdf.tags=visualizar,ler,anotar,texto,imagem home.multiTool.title=Multiferramenta de PDF -home.multiTool.desc=Mesclar, girar, reorganizar e remover páginas -multiTool.tags=Multiferramenta,Múltiplas operações,Interface do Usuário,Clique e arraste,front-end,lado do cliente,interativo,intratável,movimento +home.multiTool.desc=Mesclar, girar, reorganizar, dividir e remover páginas +multiTool.tags=Multiferramenta, múltiplas operações, Interface do Usuário, Clique e arraste, front-end, lado do cliente, interativo, intratável, movimento, excluir, migrar, dividir home.merge.title=Mesclar -home.merge.desc=Mesclar facilmente vários PDFs em um só. +home.merge.desc=Mescle facilmente vários PDFs em um só. merge.tags=mesclar,Operações de Página,Back-end,lado do servidor home.split.title=Dividir @@ -269,7 +269,7 @@ home.split.desc=Dividir PDFs em vários documentos split.tags=Operações de Página,dividir,Múltiplas Páginas,cortar,lado do servidor home.rotate.title=Girar -home.rotate.desc=Girar facilmente seus PDFs. +home.rotate.desc=Gire facilmente seus PDFs. rotate.tags=Lado do servidor @@ -287,11 +287,11 @@ pdfOrganiser.tags=duplex,par,ímpar,ordenar,mover home.addImage.title=Adicionar Imagem -home.addImage.desc=Adicionar uma imagem em um local definido no PDF (Em desenvolvimento) +home.addImage.desc=Adicionar uma imagem em um local definido no PDF addImage.tags=img,jpg,imagem,foto -home.watermark.title="Adicionar Marca d'água" -home.watermark.desc="Adicionar uma marca d'água personalizada ao seu documento PDF." +home.watermark.title=Adicionar Marca d'água +home.watermark.desc=Adicionar uma marca d'água personalizada ao seu documento PDF. watermark.tags=Texto,repetindo,rótulo,próprio,direitos autorais,marca registrada,img,jpg,imagem,foto home.permissions.title=Alterar Permissões @@ -303,13 +303,13 @@ home.removePages.title=Remover home.removePages.desc=Excluir as páginas indesejadas do seu documento PDF. removePages.tags=Remover páginas,excluir páginas -home.addPassword.title=Adicionar senha +home.addPassword.title=Adicionar Senha home.addPassword.desc=Criptografar seu documento PDF com uma senha. addPassword.tags=seguro,segurança home.removePassword.title=Remover Senha home.removePassword.desc=Remover a proteção por senha do seu documento PDF. -removePassword.tags=seguro, Descriptografar, segurança, remover senha +removePassword.tags=seguro, descriptografar, segurança, remover senha home.compressPdfs.title=Comprimir home.compressPdfs.desc=Comprimir PDFs para reduzir o tamanho do arquivo. @@ -339,7 +339,7 @@ pdfToPDFA.tags=arquivo,longo prazo,padrão,conversão,armazenamento,preservaçã home.PDFToWord.title=PDF para Word home.PDFToWord.desc=Converter PDF para formatos Word (DOC, DOCX e ODT) -PDFToWord.tags=doc,docx,odt,word,transformação,formato,conversão,escritório,microsoft,arquivo doc +PDFToWord.tags=doc,docx,odt,word,transformação,formato,conversão,escritório,microsoft,doc home.PDFToPresentation.title=PDF para Apresentação home.PDFToPresentation.desc=Converter PDF para formatos de apresentação (PPT, PPTX e ODP) @@ -355,12 +355,12 @@ PDFToHTML.tags=conteúdo web,compatível com navegador home.PDFToXML.title=PDF para XML -home.PDFToXML.desc=Converter PDF para o formato XML +home.PDFToXML.desc=Converter PDF para formato XML PDFToXML.tags=extração-de-dados,conteúdo-estruturado,interoperabilidade,transformação,converter home.ScannerImageSplit.title=Detectar/Dividir Fotos Digitalizadas -home.ScannerImageSplit.desc=Divide várias fotos de dentro de uma imagem/PDF digitalizado -ScannerImageSplit.tags=separar,detecção-automática,digitalizações,foto-múltipla,organizar +home.ScannerImageSplit.desc=Divide várias fotos de dentro de uma imagem/PDF +ScannerImageSplit.tags=separar,detecção-automática,digitalizações,fotos-múltiplas,organizar home.sign.title=Assinar home.sign.desc=Adicionar assinatura ao PDF por desenho, texto ou imagem @@ -378,7 +378,7 @@ home.removeBlanks.title=Remover Páginas em Branco home.removeBlanks.desc=Detectar e remover páginas em branco de um documento removeBlanks.tags=limpeza,otimização,sem-conteúdo,organizar -home.removeAnnotations.title=Remover anotações +home.removeAnnotations.title=Remover Anotações home.removeAnnotations.desc=Remove todos os comentários/anotações de um PDF removeAnnotations.tags=comentários,destaque,notas,marcação,remover @@ -426,7 +426,7 @@ home.autoSplitPDF.title=Divisão Automática de Páginas home.autoSplitPDF.desc=Dividir automaticamente um PDF digitalizado com separador de páginas físicas QR Code autoSplitPDF.tags=baseado-em-QR,separar,segmento-de-digitalização,organizar -home.sanitizePdf.title=Sanitizar +home.sanitizePdf.title=Higienizar home.sanitizePdf.desc=Remover scripts e outros elementos de arquivos PDF sanitizePdf.tags=limpar,seguro,protegido,remover-ameaças @@ -468,7 +468,7 @@ home.autoRedact.desc=Ocultação automática (escurecimento) de texto em um PDF autoRedact.tags=Redigir,ocultar,escurecer,preto,marcador,oculto home.tableExtraxt.title=PDF para CSV -home.tableExtraxt.desc=Extrai tabelas de um PDF convertendo-o para CSV +home.tableExtraxt.desc=Extrai tabelas de um PDF convertendo para CSV tableExtraxt.tags=CSV,extração de tabela,extrair,converter @@ -481,13 +481,13 @@ home.overlay-pdfs.title=Sobrepor PDFs home.overlay-pdfs.desc=Sobrepõe PDFs sobre outro PDF overlay-pdfs.tags=Sobreposição -home.split-by-sections.title=Dividir PDF por seções +home.split-by-sections.title=Dividir PDF por Seções home.split-by-sections.desc=Divida cada página de um PDF em seções horizontais e verticais menores split-by-sections.tags=Seção Dividir, Dividir, Personalizar -home.AddStampRequest.title=Adicionar carimbo ao PDF +home.AddStampRequest.title=Adicionar Carimbo ao PDF home.AddStampRequest.desc=Adicione texto ou carimbos de imagem em locais definidos -AddStampRequest.tags="Carimbo,Adicionar imagem,centralizar imagem,Marca d'água,PDF,Incorporar,Personalizar" +AddStampRequest.tags=Carimbo,Adicionar imagem,centralizar imagem,Marca d'água,PDF,Incorporar,Personalizar home.PDFToBook.title=PDF para Livro @@ -495,36 +495,36 @@ home.PDFToBook.desc=Converte PDF para formatos de livro/quadrinhos usando Calibr PDFToBook.tags=Livro,Quadrinhos,Calibre,Converter,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf home.BookToPDF.title=Livro para PDF -home.BookToPDF.desc=Converte formatos de livros/quadrinhos em PDF usando Calibre +home.BookToPDF.desc=Converte formatos de livros/quadrinhos para PDF usando Calibre BookToPDF.tags=Livro,Quadrinhos,Calibre,Converter,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf -home.removeImagePdf.title=Remover imagem +home.removeImagePdf.title=Remover Imagem home.removeImagePdf.desc=Remova a imagem do PDF para reduzir o tamanho do arquivo removeImagePdf.tags=Remover imagem,operações de página,back-end,lado do servidor -home.splitPdfByChapters.title=Split PDF by Chapters -home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. -splitPdfByChapters.tags=split,chapters,bookmarks,organize +home.splitPdfByChapters.title=Divide PDF por Capítulos +home.splitPdfByChapters.desc=Divide um PDF em vários arquivos baseado na sua estrutura de capítulos. +splitPdfByChapters.tags=dividir,capítulos,favoritos,organizar #replace-invert-color -replace-color.title=Replace-Invert-Color -replace-color.header=Replace-Invert Color PDF -home.replaceColorPdf.title=Replace and Invert Color -home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size -replaceColorPdf.tags=Replace Color,Page operations,Back end,server side -replace-color.selectText.1=Replace or Invert color Options -replace-color.selectText.2=Default(Default high contrast colors) -replace-color.selectText.3=Custom(Customized colors) -replace-color.selectText.4=Full-Invert(Invert all colors) -replace-color.selectText.5=High contrast color options -replace-color.selectText.6=white text on black background -replace-color.selectText.7=Black text on white background -replace-color.selectText.8=Yellow text on black background -replace-color.selectText.9=Green text on black background -replace-color.selectText.10=Choose text Color -replace-color.selectText.11=Choose background Color -replace-color.submit=Replace +replace-color.title=Substituir-Inverter-Cor +replace-color.header=Substitui-Inverter Cor PDF +home.replaceColorPdf.title=Substitui e Inverte Cor +home.replaceColorPdf.desc=Substitui cor de um texto e plano de fundo de um PDF e inverte a toda cor do PDF para reduzir o tamanho +replaceColorPdf.tags=Substitui Cor, Operações na Página, back end, lado do servidor +replace-color.selectText.1=Substituir ou inverter cor Opções +replace-color.selectText.2=Padrão(Padrão cores de alto constraste) +replace-color.selectText.3=Customizado(Cores customizadas) +replace-color.selectText.4=Inversão Completa(Inverte todas cores) +replace-color.selectText.5=Opções de cores de alto contraste +replace-color.selectText.6=Texto branco em um plano de fundo preto +replace-color.selectText.7=Texto preto em um plano de fundo branco +replace-color.selectText.8=Texto amarelo em um plano de fundo preto +replace-color.selectText.9=Texto verde em um plano de fundo preto +replace-color.selectText.10=Escolha cor do texto +replace-color.selectText.11=Escolha cor do plano de fundo +replace-color.submit=Substituir @@ -541,24 +541,24 @@ login.rememberme=Lembrar de mim login.invalid=Usuário ou senha inválidos. login.locked=Sua conta foi bloqueada. login.signinTitle=Por favor, inicie a sessão -login.ssoSignIn=Iniciar sessão através de início de sessão único +login.ssoSignIn=Iniciar sessão através de login único (SSO) login.oauth2AutoCreateDisabled=Auto-Criar Usuário OAUTH2 Desativado login.oauth2AdminBlockedUser=O registro ou login de usuários não registrados está atualmente bloqueado. Entre em contato com o administrador. login.oauth2RequestNotFound=Solicitação de autorização não encontrada login.oauth2InvalidUserInfoResponse=Resposta de informação de usuário inválida -login.oauth2invalidRequest=Requisição inválida -login.oauth2AccessDenied=Acesso negado -login.oauth2InvalidTokenResponse=Resposta de token inválida -login.oauth2InvalidIdToken=Id de token inválido +login.oauth2invalidRequest=Requisição Inválida +login.oauth2AccessDenied=Acesso Negado +login.oauth2InvalidTokenResponse=Resposta de Token Inválida +login.oauth2InvalidIdToken=Id de Token Inválido login.userIsDisabled=O usuário está desativado, o login está atualmente bloqueado com este nome de usuário. Entre em contato com o administrador. -login.alreadyLoggedIn=You are already logged in to -login.alreadyLoggedIn2=devices. Please log out of the devices and try again. -login.toManySessions=You have too many active sessions -login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. +login.alreadyLoggedIn=Você já está conectado +login.alreadyLoggedIn2=aparelhos. Por favor saia dos aparelhos e tente novamente. +login.toManySessions=Você tem muitas sessões ativas +login.toManySessions2=Por favor saida dos aparelhos e tente novamente. Alternativamente você pode adquirir Stirling PDF Pro. #auto-redact -autoRedact.title=Redigir automaticamente -autoRedact.header=Redigir automaticamente +autoRedact.title=Redação Automática de Dados +autoRedact.header=Redação Automática de Dados autoRedact.colorLabel=Cor autoRedact.textsToRedactLabel=Texto para redigir (separado por linha) autoRedact.textsToRedactPlaceholder=por exemplo: \nConfidencial \nSecreto @@ -644,22 +644,22 @@ AddStampRequest.fontSize=Tamanho da fonte/imagem AddStampRequest.rotation=Rotação AddStampRequest.opacity=Opacidade AddStampRequest.position=Posição -AddStampRequest.overrideX=Substituir coordenada X -AddStampRequest.overrideY=Substituir coordenada Y +AddStampRequest.overrideX=Substituir Coordenada X +AddStampRequest.overrideY=Substituir Coordenada Y AddStampRequest.customMargin=Margem personalizada AddStampRequest.customColor=Cor de texto personalizada AddStampRequest.submit=Enviar #sanitizePDF -sanitizePDF.title=Sanitizar PDF -sanitizePDF.header=Sanitizar um arquivo PDF +sanitizePDF.title=Higienizar PDF +sanitizePDF.header=Higienizar um arquivo PDF sanitizePDF.selectText.1=Remover ações de JavaScript sanitizePDF.selectText.2=Remover arquivos embutidos sanitizePDF.selectText.3=Remover metadados sanitizePDF.selectText.4=Remover links sanitizePDF.selectText.5=Remover fontes -sanitizePDF.submit=Sanitizar PDF +sanitizePDF.submit=Higienizar PDF #addPageNumbers @@ -739,9 +739,9 @@ certSign.title=Assinatura com Certificado certSign.header=Assine um PDF com o seu certificado (Em desenvolvimento) certSign.selectPDF=Selecione um arquivo PDF para assinatura: certSign.jksNote=Nota: Se o seu tipo de certificado não estiver listado abaixo, converta-o em um arquivo Java Keystore (.jks) usando a ferramenta de linha de comando keytool. Em seguida, escolha a opção de arquivo .jks abaixo. -certSign.selectKey="Selecione o seu arquivo de chave privada (formato PKCS#8, pode ser .pem ou .der):" +certSign.selectKey=Selecione o seu arquivo de chave privada (formato PKCS#8, pode ser .pem ou .der): certSign.selectCert=Selecione o seu arquivo de certificado (formato X.509, pode ser .pem ou .der): -certSign.selectP12="Selecione o seu arquivo de armazenamento de chave PKCS#12 (.p12 ou .pfx) (opcional, se fornecido, deve conter a sua chave privada e certificado):" +certSign.selectP12=Selecione o seu arquivo de armazenamento de chave PKCS#12 (.p12 ou .pfx) (opcional, se fornecido, deve conter a sua chave privada e certificado): certSign.selectJKS=Selecione seu arquivo Java Keystore (.jks ou .keystore): certSign.certType=Tipo de Certificado certSign.password=Digite a senha do seu armazenamento de chave ou chave privada (se aplicável): @@ -753,7 +753,7 @@ certSign.submit=Assinar PDF #removeCertSign -removeCertSign.title=Remover assinatura com Certificado +removeCertSign.title=Remover Assinatura do Certificado removeCertSign.header=Remover o certificado digital do PDF removeCertSign.selectPDF=Selecione um arquivo PDF: removeCertSign.submit=Remover assinatura @@ -765,13 +765,13 @@ removeBlanks.header=Remover páginas em branco removeBlanks.threshold=Limite de brancura de pixel: removeBlanks.thresholdDesc=Limite para determinar o quão branco um pixel branco deve ser para ser classificado como 'Branco'. 0 = Preto, 255 branco puro. removeBlanks.whitePercent=Porcentagem de Branco (%): -removeBlanks.whitePercentDesc=Porcentagem de páginas que devem ter pixels “brancos” para serem removidas +removeBlanks.whitePercentDesc=Porcentagem da página que devem ter pixels “brancos” para serem removidas removeBlanks.submit=Remover páginas em branco #removeAnnotations -removeAnnotations.title=Remover anotações -removeAnnotations.header=Remover anotações +removeAnnotations.title=Remover Anotações +removeAnnotations.header=Remover Anotações removeAnnotations.submit=Remover @@ -837,26 +837,26 @@ ScannerImageSplit.info=Python não está instalado. É necessário para executar #OCR ocr.title=OCR / Limpeza de Digitalização ocr.header=OCR / Limpeza de Digitalização (Reconhecimento Óptico de Caracteres) -ocr.selectText.1=Selecione os idiomas a serem detectados no PDF (os listados são os atualmente detectados): -ocr.selectText.2=Criar um arquivo de texto contendo o texto OCR ao lado do PDF com OCR +ocr.selectText.1=Selecione os idiomas a serem detectados no PDF (os listados são os atualmente instalados): +ocr.selectText.2=Criar um arquivo de texto contendo o texto OCR junto do PDF com OCR ocr.selectText.3=Páginas corretamente digitalizadas em um ângulo inclinado, gire-as de volta à posição original ocr.selectText.4=Limpar a página para reduzir a probabilidade de o OCR encontrar texto no ruído de fundo (sem alteração na saída) ocr.selectText.5=Limpar a página para reduzir a probabilidade de o OCR encontrar texto no ruído de fundo, mantendo a limpeza na saída. -ocr.selectText.6=Ignorar páginas com texto interativo, processar apenas as páginas de OCR que são imagens +ocr.selectText.6=Ignorar páginas com texto interativo, processar por OCR apenas as páginas com imagens ocr.selectText.7=Forçar OCR, executar OCR em todas as páginas, removendo todos os elementos de texto originais ocr.selectText.8=Normal (gerará um erro se o PDF já contiver texto) -ocr.selectText.9=Configurações adicionais +ocr.selectText.9=Configurações Adicionais ocr.selectText.10=Modo OCR ocr.selectText.11=Remover imagens após o OCR (remove TODAS as imagens, útil apenas como parte do processo de conversão) -ocr.selectText.12=Tipo de renderização (avançado) +ocr.selectText.12=Tipo de Renderização (avançado) ocr.help=Por favor, leia a documentação sobre como usar isso para outros idiomas e/ou fora do ambiente Docker ocr.credit=Este serviço usa OCRmyPDF e Tesseract para OCR. ocr.submit=Processar PDF com OCR #extractImages -extractImages.title=Extrair imagens -extractImages.header=Extrair imagens +extractImages.title=Extrair Imagens +extractImages.header=Extrair Imagens extractImages.selectText=Selecione o formato de imagem para converter as imagens extraídas extractImages.allowDuplicates=Salvar imagens duplicadas extractImages.submit=Extrair @@ -884,7 +884,7 @@ compress.submit=Comprimir #Add image -addImage.title=Adicionar imagem +addImage.title=Adicionar Imagem addImage.header=Adicionar imagem ao PDF addImage.everyPage=Para cada página? addImage.upload=Enviar imagem @@ -893,7 +893,7 @@ addImage.submit=Adicionar imagem #merge merge.title=Mesclar -merge.header=Mesclar Vários PDFs (2+) +merge.header=Mesclar vários PDFs (2+) merge.sortByName=Classificar por nome merge.sortByDate=Classificar por data merge.removeCertSign=Remover a assinatura digital do arquivo mesclado? @@ -907,7 +907,7 @@ pdfOrganiser.submit=Reorganizar páginas pdfOrganiser.mode=Modo pdfOrganiser.mode.1=Ordem de página personalizada pdfOrganiser.mode.2=Ordem inversa -pdfOrganiser.mode.3=Classificação Duplex +pdfOrganiser.mode.3=Classificação duplex pdfOrganiser.mode.4=Classificação de livreto pdfOrganiser.mode.5=Classificação de livreto com ponto lateral pdfOrganiser.mode.6=Divisão ímpar-par @@ -972,7 +972,7 @@ imageToPDF.selectText.5=Converter em PDFs separados #pdfToImage -pdfToImage.title=PDF para imagem +pdfToImage.title=PDF para Imagem pdfToImage.header=Converter PDF para imagem pdfToImage.selectText=Formato de imagem pdfToImage.singleOrMultiple=Tipo de resultado de imagem @@ -981,7 +981,7 @@ pdfToImage.multi=Várias imagens, uma imagem por página pdfToImage.colorType=Tipo de cor pdfToImage.color=Colorida pdfToImage.grey=Escala de Cinza -pdfToImage.blackwhite=Preto e Branco (pode perder de dados!) +pdfToImage.blackwhite=Preto e Branco (pode perder informações!) pdfToImage.submit=Converter pdfToImage.info=Python não está instalado. Necessário para conversão WebP. @@ -992,7 +992,7 @@ addPassword.header=Adicionar Senha (Criptografar) addPassword.selectText.1=Selecione o PDF para Criptografar addPassword.selectText.2=Senha addPassword.selectText.3=Tamanho da Chave de Criptografia -addPassword.selectText.4=Valores mais altos são mais seguros, mas valores mais baixos são mais compatíveis. +addPassword.selectText.4=Valores mais altos são mais seguros, mas valores mais baixos são melhores para compatibilidade. addPassword.selectText.5=Permissões a serem definidas (recomendado para uso junto com a senha do proprietário) addPassword.selectText.6=Impedir a montagem do documento addPassword.selectText.7=Impedir a extração de conteúdo @@ -1009,26 +1009,26 @@ addPassword.submit=Criptografar #watermark -watermark.title="Adicionar marca d'água" -watermark.header="Adicionar marca d'água" -watermark.selectText.1="Selecione PDF para adicionar a marca d'água:" -watermark.selectText.2="Texto da marca d'água:" +watermark.title=Adicionar marca d'água +watermark.header=Adicionar marca d'água +watermark.selectText.1=Selecione PDF para adicionar a marca d'água: +watermark.selectText.2=Texto da marca d'água: watermark.selectText.3=Tamanho da fonte: watermark.selectText.4=Rotação (0-360): -watermark.selectText.5="widthSpacer (Espaço entre cada marca d'água horizontalmente):" -watermark.selectText.6="heightSpacer (Espaço entre cada marca d'água verticalmente):" +watermark.selectText.5=widthSpacer (Espaço entre cada marca d'água horizontalmente): +watermark.selectText.6=heightSpacer (Espaço entre cada marca d'água verticalmente): watermark.selectText.7=Opacidade (0% - 100%): -watermark.selectText.8="Tipo de marca d'água:" -watermark.selectText.9="Imagem da marca d'água:" +watermark.selectText.8=Tipo de marca d'água: +watermark.selectText.9=Imagem da marca d'água: watermark.selectText.10=Converter PDF em imagem PDF -watermark.submit="Adicionar marca d'água" +watermark.submit=Adicionar marca d'água watermark.type.1=Texto watermark.type.2=Imagem #Change permissions -permissions.title=Alterar permissões -permissions.header=Alterar permissões +permissions.title=Alterar Permissões +permissions.header=Alterar Permissões permissions.warning=Aviso: para que essas permissões sejam imutáveis, é recomendável defini-las com uma senha através da página Adicionar Senha permissions.selectText.1=Selecione o PDF para alterar as permissões permissions.selectText.2=Permissões para definir @@ -1044,7 +1044,7 @@ permissions.submit=Alterar #remove password -removePassword.title=Remover senha +removePassword.title=Remover Senha removePassword.header=Remover senha (descriptografar) removePassword.selectText.1=Selecione o PDF para descriptografar removePassword.selectText.2=Senha @@ -1052,7 +1052,7 @@ removePassword.submit=Remover #changeMetadata -changeMetadata.title=Alterar metadados +changeMetadata.title=Alterar Metadados changeMetadata.header=Alterar metadados changeMetadata.selectText.1=Edite as variáveis ​​que deseja alterar changeMetadata.selectText.2=Excluir todos os metadados @@ -1136,7 +1136,7 @@ split-by-size-or-count.submit=Enviar #overlay-pdfs -overlay-pdfs.header=Sobrepor arquivos PDF +overlay-pdfs.header=Sobrepor Arquivos PDF overlay-pdfs.baseFile.label=Selecione o arquivo PDF base overlay-pdfs.overlayFiles.label=Selecione os arquivos PDF para sobreposição overlay-pdfs.mode.label=Selecione o modo de sobreposição @@ -1154,7 +1154,7 @@ overlay-pdfs.submit=Enviar #split-by-sections split-by-sections.title=Dividir PDF por seções split-by-sections.header=Divida o PDF em seções -split-by-sections.horizontal.label=Divisões horizontais +split-by-sections.horizontal.label=Divisões Horizontais split-by-sections.vertical.label=Divisões Verticais split-by-sections.horizontal.placeholder=Insira o número de divisões horizontais split-by-sections.vertical.placeholder=Insira o número de divisões verticais @@ -1172,8 +1172,8 @@ printFile.submit=Imprimir #licenses licenses.nav=Licenças -licenses.title=Licenças de terceiros -licenses.header=Licenças de terceiros +licenses.title=Licenças de Terceiros +licenses.header=Licenças de Terceiros licenses.module=Módulo licenses.version=Versão licenses.license=Licença @@ -1182,10 +1182,10 @@ licenses.license=Licença survey.nav=Pesquisa survey.title=Pesquisa Stirling-PDF survey.description=Stirling-PDF não tem rastreamento, então queremos ouvir nossos usuários para melhorar o Stirling-PDF! -survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here: -survey.changes2=With these changes we are getting paid business support and funding +survey.changes=Stirling-PDF mudou desde o a última pesquisa! Para saber mais acesse nosso post no blog: +survey.changes2=Com essas mudanças estamos implementando suporte empresarial pago e financeamento survey.please=Por favor, considere responder à nossa pesquisa! -survey.disabled=(O pop-up da pesquisa será desativado nas atualizações seguintes, mas estará disponível no final da página) +survey.disabled=(O pop-up da pesquisa será desativado nas atualizações seguintes, mas estará disponível no rodapé da página) survey.button=Responda a pesquisa survey.dontShowAgain=Não mostre novamente @@ -1193,11 +1193,11 @@ survey.dontShowAgain=Não mostre novamente #error error.sorry=Desculpe pelo problema! error.needHelp=Precisa de ajuda / Encontrou um problema? -error.contactTip=Se você ainda estiver com problemas, não hesite em entrar em contato conosco para obter ajuda. Você pode enviar um ticket em nossa página GitHub ou entrar em contato conosco através do Discord: +error.contactTip=Se você ainda estiver com problemas, não hesite em entrar em contato conosco para obter ajuda. Você pode enviar um tíquete em nossa página GitHub ou entrar em contato conosco através do Discord: error.404.head=404 - Página não encontrada | Ops, tropeçamos no código! error.404.1=Não conseguimos encontrar a página que você está procurando. error.404.2=Algo deu errado -error.github=Submeter um ticket no GitHub +error.github=Submeter um tíquete no GitHub error.showStack=Mostrar rastreamento de pilha error.copyStack=Copiar rastreamento de pilha error.githubSubmit=GitHub - Submeter um tíquete @@ -1205,21 +1205,21 @@ error.discordSubmit=Discord - Submeter um post de suporte #remove-image -removeImage.title=Remover imagem +removeImage.title=Remover Imagem removeImage.header=Remover imagem removeImage.removeImage=Remover imagem removeImage.submit=Remover imagem -splitByChapters.title=Split PDF by Chapters -splitByChapters.header=Split PDF by Chapters -splitByChapters.bookmarkLevel=Bookmark Level -splitByChapters.includeMetadata=Include Metadata -splitByChapters.allowDuplicates=Allow Duplicates -splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. -splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). -splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. -splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. -splitByChapters.submit=Split PDF +splitByChapters.title=Dividir PDF por Capítulos +splitByChapters.header=Dividir PDF por Capítulos +splitByChapters.bookmarkLevel=Nível de Marcador +splitByChapters.includeMetadata=Incluir Metadados +splitByChapters.allowDuplicates=Permitir Cópias +splitByChapters.desc.1=Essa ferramenta divide um arquivo PDF em vários arquivos PDFs baseado na estrutura de cápitulos. +splitByChapters.desc.2=Nível de Marcador: Escolha o nível de marcador a ser usado para divisão (0 para o primeiro nível, 1 para o segundo nível, etc). +splitByChapters.desc.3=Incluir Metadados: Se marcado, os metadados do PDF original serão incluidos em cada divisão do PDF. +splitByChapters.desc.4=Permitir Cópias: Se marcado, habilita vários marcadores na mesma página para criar PDFs separados. +splitByChapters.submit=Dividir PDF From 8aadef1412afd9948c35399657cb963822ed0eca Mon Sep 17 00:00:00 2001 From: albanobattistella <34811668+albanobattistella@users.noreply.github.com> Date: Fri, 18 Oct 2024 00:42:58 +0200 Subject: [PATCH 15/41] Update messages_it_IT.properties (#2022) --- src/main/resources/messages_it_IT.properties | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/resources/messages_it_IT.properties b/src/main/resources/messages_it_IT.properties index be40b1c0c91..2fb9b56fcc7 100644 --- a/src/main/resources/messages_it_IT.properties +++ b/src/main/resources/messages_it_IT.properties @@ -503,9 +503,9 @@ home.removeImagePdf.desc=Rimuovi le immagini dal PDF per ridurre la dimensione d removeImagePdf.tags=Rimuovi immagine,operazioni sulla pagina,back-end,lato server -home.splitPdfByChapters.title=Split PDF by Chapters -home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure. -splitPdfByChapters.tags=split,chapters,bookmarks,organize +home.splitPdfByChapters.title=Dividi PDF per capitoli +home.splitPdfByChapters.desc=Dividi un PDF in più file in base alla struttura dei capitoli. +splitPdfByChapters.tags=dividi, capitoli, segnalibri, organizza #replace-invert-color replace-color.title=Sostituisci-Inverti-Colore @@ -551,10 +551,10 @@ login.oauth2AccessDenied=Accesso negato login.oauth2InvalidTokenResponse=Risposta token non valida login.oauth2InvalidIdToken=Id Token non valido login.userIsDisabled=L'utente è disattivato, l'accesso è attualmente bloccato con questo nome utente. Si prega di contattare l'amministratore. -login.alreadyLoggedIn=You are already logged in to -login.alreadyLoggedIn2=devices. Please log out of the devices and try again. -login.toManySessions=You have too many active sessions -login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro. +login.alreadyLoggedIn=Hai già effettuato l'accesso a +login.alreadyLoggedIn2=dispositivi. Esci dai dispositivi e riprova. +login.toManySessions=Hai troppe sessioni attive +login.toManySessions2=Si prega di uscire dai dispositivi e riprovare. In alternativa, è possibile effettuare l'upgrade a Stirling PDF Pro. #auto-redact autoRedact.title=Redazione automatica From 8e9acdd053dad40c8c2afd1b0fd12e7744a4c2af Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:47:11 +0100 Subject: [PATCH 16/41] :memo: Update README: Translation Progress Table (#2031) :memo: Sync README > Made via sync_files.yml Co-authored-by: github-actions[bot] --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e717dc1b6d3..29cecc59148 100644 --- a/README.md +++ b/README.md @@ -189,13 +189,13 @@ Stirling PDF currently supports 38! | Hungarian (Magyar) (hu_HU) | ![69%](https://geps.dev/progress/69) | | Indonesia (Bahasa Indonesia) (id_ID) | ![70%](https://geps.dev/progress/70) | | Irish (Gaeilge) (ga_IE) | ![90%](https://geps.dev/progress/90) | -| Italian (Italiano) (it_IT) | ![98%](https://geps.dev/progress/98) | +| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) | | Japanese (日本語) (ja_JP) | ![87%](https://geps.dev/progress/87) | | Korean (한국어) (ko_KR) | ![77%](https://geps.dev/progress/77) | | Norwegian (Norsk) (no_NB) | ![90%](https://geps.dev/progress/90) | | Polish (Polski) (pl_PL) | ![84%](https://geps.dev/progress/84) | | Portuguese (Português) (pt_PT) | ![72%](https://geps.dev/progress/72) | -| Portuguese Brazilian (Português) (pt_BR) | ![94%](https://geps.dev/progress/94) | +| Portuguese Brazilian (Português) (pt_BR) | ![99%](https://geps.dev/progress/99) | | Romanian (Română) (ro_RO) | ![92%](https://geps.dev/progress/92) | | Russian (Русский) (ru_RU) | ![77%](https://geps.dev/progress/77) | | Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![72%](https://geps.dev/progress/72) | @@ -204,7 +204,7 @@ Stirling PDF currently supports 38! | Spanish (Español) (es_ES) | ![93%](https://geps.dev/progress/93) | | Swedish (Svenska) (sv_SE) | ![92%](https://geps.dev/progress/92) | | Thai (ไทย) (th_TH) | ![91%](https://geps.dev/progress/91) | -| Traditional Chinese (繁體中文) (zh_TW) | ![90%](https://geps.dev/progress/90) | +| Traditional Chinese (繁體中文) (zh_TW) | ![99%](https://geps.dev/progress/99) | | Turkish (Türkçe) (tr_TR) | ![94%](https://geps.dev/progress/94) | | Ukrainian (Українська) (uk_UA) | ![82%](https://geps.dev/progress/82) | | Vietnamese (Tiếng Việt) (vi_VN) | ![90%](https://geps.dev/progress/90) | From 46a6a585a9c03b9219690c3a313f7da121340566 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:48:06 +0100 Subject: [PATCH 17/41] Bump imageioVersion from 3.11.0 to 3.12.0 (#1998) Bumps `imageioVersion` from 3.11.0 to 3.12.0. Updates `com.twelvemonkeys.imageio:imageio-batik` from 3.11.0 to 3.12.0 Updates `com.twelvemonkeys.imageio:imageio-bmp` from 3.11.0 to 3.12.0 Updates `com.twelvemonkeys.imageio:imageio-jpeg` from 3.11.0 to 3.12.0 Updates `com.twelvemonkeys.imageio:imageio-tiff` from 3.11.0 to 3.12.0 Updates `com.twelvemonkeys.imageio:imageio-webp` from 3.11.0 to 3.12.0 --- updated-dependencies: - dependency-name: com.twelvemonkeys.imageio:imageio-batik dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.twelvemonkeys.imageio:imageio-bmp dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.twelvemonkeys.imageio:imageio-jpeg dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.twelvemonkeys.imageio:imageio-tiff dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.twelvemonkeys.imageio:imageio-webp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f468388283a..f3ebc8d853d 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ ext { springBootVersion = "3.3.4" pdfboxVersion = "3.0.3" logbackVersion = "1.5.7" - imageioVersion = "3.11.0" + imageioVersion = "3.12.0" lombokVersion = "1.18.34" bouncycastleVersion = "1.78.1" } From fe92f99093487cfe1e02b24b3e254b4f8fc011fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:48:41 +0100 Subject: [PATCH 18/41] Bump org.apache.xmlgraphics:batik-all from 1.17 to 1.18 (#2018) Bumps org.apache.xmlgraphics:batik-all from 1.17 to 1.18. --- updated-dependencies: - dependency-name: org.apache.xmlgraphics:batik-all dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f3ebc8d853d..d74a0026200 100644 --- a/build.gradle +++ b/build.gradle @@ -153,7 +153,7 @@ dependencies { testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion" // Batik - implementation "org.apache.xmlgraphics:batik-all:1.17" + implementation "org.apache.xmlgraphics:batik-all:1.18" // TwelveMonkeys runtimeOnly "com.twelvemonkeys.imageio:imageio-batik:$imageioVersion" From 094ed12b85602b74902ac022cbd9e9cc6acd285a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:48:53 +0100 Subject: [PATCH 19/41] Bump io.micrometer:micrometer-core from 1.13.4 to 1.13.6 (#2019) Bumps [io.micrometer:micrometer-core](https://github.com/micrometer-metrics/micrometer) from 1.13.4 to 1.13.6. - [Release notes](https://github.com/micrometer-metrics/micrometer/releases) - [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.13.4...v1.13.6) --- updated-dependencies: - dependency-name: io.micrometer:micrometer-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d74a0026200..022dd5de8ee 100644 --- a/build.gradle +++ b/build.gradle @@ -195,7 +195,7 @@ dependencies { implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion" implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion" - implementation "io.micrometer:micrometer-core:1.13.4" + implementation "io.micrometer:micrometer-core:1.13.6" implementation group: "com.google.zxing", name: "core", version: "3.5.3" // https://mvnrepository.com/artifact/org.commonmark/commonmark implementation "org.commonmark:commonmark:0.23.0" From 376ec865b841db78ec672c139ce2ba68b553876f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:50:06 +0100 Subject: [PATCH 20/41] Update 3rd Party Licenses (#2032) Signed-off-by: GitHub Action Co-authored-by: GitHub Action --- .../resources/static/3rdPartyLicenses.json | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/resources/static/3rdPartyLicenses.json b/src/main/resources/static/3rdPartyLicenses.json index 9ff496f225a..6047eaa6971 100644 --- a/src/main/resources/static/3rdPartyLicenses.json +++ b/src/main/resources/static/3rdPartyLicenses.json @@ -263,61 +263,61 @@ }, { "moduleName": "com.twelvemonkeys.common:common-image", - "moduleVersion": "3.11.0", + "moduleVersion": "3.12.0", "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, { "moduleName": "com.twelvemonkeys.common:common-io", - "moduleVersion": "3.11.0", + "moduleVersion": "3.12.0", "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, { "moduleName": "com.twelvemonkeys.common:common-lang", - "moduleVersion": "3.11.0", + "moduleVersion": "3.12.0", "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, { "moduleName": "com.twelvemonkeys.imageio:imageio-batik", - "moduleVersion": "3.11.0", + "moduleVersion": "3.12.0", "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, { "moduleName": "com.twelvemonkeys.imageio:imageio-bmp", - "moduleVersion": "3.11.0", + "moduleVersion": "3.12.0", "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, { "moduleName": "com.twelvemonkeys.imageio:imageio-core", - "moduleVersion": "3.11.0", + "moduleVersion": "3.12.0", "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, { "moduleName": "com.twelvemonkeys.imageio:imageio-jpeg", - "moduleVersion": "3.11.0", + "moduleVersion": "3.12.0", "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, { "moduleName": "com.twelvemonkeys.imageio:imageio-metadata", - "moduleVersion": "3.11.0", + "moduleVersion": "3.12.0", "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, { "moduleName": "com.twelvemonkeys.imageio:imageio-tiff", - "moduleVersion": "3.11.0", + "moduleVersion": "3.12.0", "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, { "moduleName": "com.twelvemonkeys.imageio:imageio-webp", - "moduleVersion": "3.11.0", + "moduleVersion": "3.12.0", "moduleLicense": "The BSD License", "moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license" }, @@ -687,14 +687,14 @@ }, { "moduleName": "org.apache.xmlgraphics:batik-all", - "moduleVersion": "1.17", + "moduleVersion": "1.18", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, { "moduleName": "org.apache.xmlgraphics:xmlgraphics-commons", "moduleUrl": "http://xmlgraphics.apache.org/commons/", - "moduleVersion": "2.9", + "moduleVersion": "2.10", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, From ca535b0abe7a820e15441859f239fde574c334ef Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:57:01 +0100 Subject: [PATCH 21/41] Update 3rd Party Licenses (#2033) Signed-off-by: GitHub Action Co-authored-by: GitHub Action --- src/main/resources/static/3rdPartyLicenses.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/static/3rdPartyLicenses.json b/src/main/resources/static/3rdPartyLicenses.json index 6047eaa6971..962598568ee 100644 --- a/src/main/resources/static/3rdPartyLicenses.json +++ b/src/main/resources/static/3rdPartyLicenses.json @@ -400,7 +400,7 @@ { "moduleName": "io.micrometer:micrometer-core", "moduleUrl": "https://github.com/micrometer-metrics/micrometer", - "moduleVersion": "1.13.4", + "moduleVersion": "1.13.6", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, From b31564968c5e7c085c643caf929d3057e65da5b3 Mon Sep 17 00:00:00 2001 From: "pixeebot[bot]" <104101892+pixeebot[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 00:10:42 +0100 Subject: [PATCH 22/41] Introduced protections against system command injection (#2011) * Introduced protections against system command injection * Update translation files (#2034) Signed-off-by: GitHub Action Co-authored-by: GitHub Action --------- Signed-off-by: GitHub Action Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com> Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub Action --- src/main/java/stirling/software/SPDF/SPdfApplication.java | 4 ++-- src/main/resources/messages_pt_BR.properties | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/stirling/software/SPDF/SPdfApplication.java b/src/main/java/stirling/software/SPDF/SPdfApplication.java index 7149088b614..90d496d0689 100644 --- a/src/main/java/stirling/software/SPDF/SPdfApplication.java +++ b/src/main/java/stirling/software/SPDF/SPdfApplication.java @@ -78,9 +78,9 @@ public void init() { // For Windows SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url); } else if (os.contains("mac")) { - rt.exec("open " + url); + SystemCommand.runCommand(rt, "open " + url); } else if (os.contains("nix") || os.contains("nux")) { - rt.exec("xdg-open " + url); + SystemCommand.runCommand(rt, "xdg-open " + url); } } catch (Exception e) { logger.error("Error opening browser: {}", e.getMessage()); diff --git a/src/main/resources/messages_pt_BR.properties b/src/main/resources/messages_pt_BR.properties index 6c263ad8f7b..bfbccaeebcf 100644 --- a/src/main/resources/messages_pt_BR.properties +++ b/src/main/resources/messages_pt_BR.properties @@ -125,7 +125,7 @@ enterpriseEdition.ssoAdvert=Procurando por mais recursos de controle de usuário ################# analytics.title=Você quer melhorar Stirling PDF? analytics.paragraph1=Stirling PDF possui coleta de dados opcional para ajudar a melhorar o produto. Nós não rastreamos nenhuma informação pessoal ou conteúdo dos arquivos. -analytics.paragraph2=Por favor considere habilitar coleta de dados para ajudar Stirling-PDF a crescer e nos ajudar a entender nossos usuários melhor. +analytics.paragraph2=Por favor considere habilitar coleta de dados para ajudar Stirling-PDF a crescer e nos ajudar a entender nossos usuários melhor. analytics.enable=Habilitar coleta de dados analytics.disable=Desabilitar coleta de dados analytics.settings=Você pode alterar as configurações de coleta de dados no arquivo config/settings.yml @@ -1182,7 +1182,7 @@ licenses.license=Licença survey.nav=Pesquisa survey.title=Pesquisa Stirling-PDF survey.description=Stirling-PDF não tem rastreamento, então queremos ouvir nossos usuários para melhorar o Stirling-PDF! -survey.changes=Stirling-PDF mudou desde o a última pesquisa! Para saber mais acesse nosso post no blog: +survey.changes=Stirling-PDF mudou desde o a última pesquisa! Para saber mais acesse nosso post no blog: survey.changes2=Com essas mudanças estamos implementando suporte empresarial pago e financeamento survey.please=Por favor, considere responder à nossa pesquisa! survey.disabled=(O pop-up da pesquisa será desativado nas atualizações seguintes, mas estará disponível no rodapé da página) @@ -1217,7 +1217,7 @@ splitByChapters.bookmarkLevel=Nível de Marcador splitByChapters.includeMetadata=Incluir Metadados splitByChapters.allowDuplicates=Permitir Cópias splitByChapters.desc.1=Essa ferramenta divide um arquivo PDF em vários arquivos PDFs baseado na estrutura de cápitulos. -splitByChapters.desc.2=Nível de Marcador: Escolha o nível de marcador a ser usado para divisão (0 para o primeiro nível, 1 para o segundo nível, etc). +splitByChapters.desc.2=Nível de Marcador: Escolha o nível de marcador a ser usado para divisão (0 para o primeiro nível, 1 para o segundo nível, etc). splitByChapters.desc.3=Incluir Metadados: Se marcado, os metadados do PDF original serão incluidos em cada divisão do PDF. splitByChapters.desc.4=Permitir Cópias: Se marcado, habilita vários marcadores na mesma página para criar PDFs separados. splitByChapters.submit=Dividir PDF From 09c9944fc35421ab0461943ee1db62597cc1fc03 Mon Sep 17 00:00:00 2001 From: "pixeebot[bot]" <104101892+pixeebot[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 07:15:10 +0100 Subject: [PATCH 23/41] Switch order of literals to prevent NullPointerException (#2035) Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com> --- src/main/java/stirling/software/SPDF/SPdfApplication.java | 2 +- .../software/SPDF/controller/api/ScalePagesController.java | 2 +- .../controller/api/converters/ConvertImgPDFController.java | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/stirling/software/SPDF/SPdfApplication.java b/src/main/java/stirling/software/SPDF/SPdfApplication.java index 90d496d0689..b184f1ef83b 100644 --- a/src/main/java/stirling/software/SPDF/SPdfApplication.java +++ b/src/main/java/stirling/software/SPDF/SPdfApplication.java @@ -37,7 +37,7 @@ public class SPdfApplication { @Value("${server.port:8080}") public void setServerPortStatic(String port) { - if (port.equalsIgnoreCase("auto")) { + if ("auto".equalsIgnoreCase(port)) { // Use Spring Boot's automatic port assignment (server.port=0) SPdfApplication.serverPortStatic = "0"; // This will let Spring Boot assign an available port diff --git a/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java b/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java index 9b7714ad2df..19e135021b2 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/ScalePagesController.java @@ -109,7 +109,7 @@ public ResponseEntity scalePages(@ModelAttribute ScalePagesRequest reque } private PDRectangle getTargetSize(String targetPDRectangle, PDDocument sourceDocument) { - if (targetPDRectangle.equals("KEEP")) { + if ("KEEP".equals(targetPDRectangle)) { if (sourceDocument.getNumberOfPages() == 0) { return null; } diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java index 8cf4246ecd8..be955dbd386 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertImgPDFController.java @@ -82,7 +82,7 @@ public ResponseEntity convertToImage(@ModelAttribute ConvertToImageReque result = PdfUtils.convertFromPdf( pdfBytes, - imageFormat.equalsIgnoreCase("webp") ? "png" : imageFormat.toUpperCase(), + "webp".equalsIgnoreCase(imageFormat) ? "png" : imageFormat.toUpperCase(), colorTypeResult, singleImage, Integer.valueOf(dpi), @@ -90,9 +90,9 @@ public ResponseEntity convertToImage(@ModelAttribute ConvertToImageReque if (result == null || result.length == 0) { logger.error("resultant bytes for {} is null, error converting ", filename); } - if (imageFormat.equalsIgnoreCase("webp") && !CheckProgramInstall.isPythonAvailable()) { + if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) { throw new IOException("Python is not installed. Required for WebP conversion."); - } else if (imageFormat.equalsIgnoreCase("webp") + } else if ("webp".equalsIgnoreCase(imageFormat) && CheckProgramInstall.isPythonAvailable()) { // Write the output stream to a temp file Path tempFile = Files.createTempFile("temp_png", ".png"); From 84abd60c4f89b17a4beabf9b2f43e7a37c1cb5dc Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Fri, 18 Oct 2024 21:34:25 +0100 Subject: [PATCH 24/41] Update PdfImageRemovalController.java --- .../software/SPDF/controller/api/PdfImageRemovalController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/stirling/software/SPDF/controller/api/PdfImageRemovalController.java b/src/main/java/stirling/software/SPDF/controller/api/PdfImageRemovalController.java index 302b00f4cad..2c937c349eb 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/PdfImageRemovalController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/PdfImageRemovalController.java @@ -21,6 +21,7 @@ */ @RestController @RequestMapping("/api/v1/general") +@Tag(name = "General", description = "General APIs") public class PdfImageRemovalController { // Service for removing images from PDFs From 227d18a469a0f7277ba478634cf2491ad222de7b Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Fri, 18 Oct 2024 22:22:44 +0100 Subject: [PATCH 25/41] bug Update remove image to show on api docs --- .../software/SPDF/controller/api/PdfImageRemovalController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/stirling/software/SPDF/controller/api/PdfImageRemovalController.java b/src/main/java/stirling/software/SPDF/controller/api/PdfImageRemovalController.java index 2c937c349eb..92bd3bace82 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/PdfImageRemovalController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/PdfImageRemovalController.java @@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.*; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.service.CustomPDDocumentFactory; From eff18430612f4a47b05c44220f321067978c8d3e Mon Sep 17 00:00:00 2001 From: Ludy Date: Sun, 20 Oct 2024 13:30:58 +0200 Subject: [PATCH 26/41] Major Enhancements to SAML2 and OAuth2 Integration with Simplified Security Configurations (#2040) * implement Saml2 login/logout * changed: deprecation code * relyingPartyRegistrations only enabled samle --- build.gradle | 16 +- .../software/SPDF/SPdfApplication.java | 17 +- .../security/CustomLogoutSuccessHandler.java | 220 +++++++- .../security/SecurityConfiguration.java | 497 ++++++++++++------ .../security/UserAuthenticationFilter.java | 7 +- .../SPDF/config/security/UserService.java | 5 + ...tomOAuth2AuthenticationFailureHandler.java | 3 +- ...tomOAuth2AuthenticationSuccessHandler.java | 5 + .../CustomOAuth2LogoutSuccessHandler.java | 122 ----- .../saml/ConvertResponseToAuthentication.java | 68 --- ...ustomSAMLAuthenticationFailureHandler.java | 51 -- ...ustomSAMLAuthenticationSuccessHandler.java | 108 ---- .../saml/SAMLLogoutSuccessHandler.java | 38 -- .../saml/Saml2AuthorityAttributeLookup.java | 7 - .../Saml2AuthorityAttributeLookupImpl.java | 17 - .../security/saml/SamlAssertionUtils.java | 63 --- .../SPDF/config/security/saml/SamlConfig.java | 42 -- .../saml/ScimSaml2AuthenticatedPrincipal.java | 89 ---- .../security/saml/SimpleScimMappings.java | 10 - .../security/saml2/CertificateUtils.java | 48 ++ .../CustomSaml2AuthenticatedPrincipal.java | 45 ++ ...stomSaml2AuthenticationFailureHandler.java | 38 ++ ...stomSaml2AuthenticationSuccessHandler.java | 91 ++++ ...mSaml2ResponseAuthenticationConverter.java | 86 +++ .../session/SessionPersistentRegistry.java | 5 + .../SPDF/controller/api/UserController.java | 3 + .../controller/web/AccountWebController.java | 68 ++- .../SPDF/model/ApplicationProperties.java | 124 ++++- .../software/SPDF/model/Provider.java | 12 - src/main/resources/settings.yml.template | 12 + src/main/resources/templates/addUsers.html | 2 +- src/main/resources/templates/login.html | 6 +- 32 files changed, 1083 insertions(+), 842 deletions(-) delete mode 100644 src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2LogoutSuccessHandler.java delete mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/ConvertResponseToAuthentication.java delete mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/CustomSAMLAuthenticationFailureHandler.java delete mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/CustomSAMLAuthenticationSuccessHandler.java delete mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/SAMLLogoutSuccessHandler.java delete mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/Saml2AuthorityAttributeLookup.java delete mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/Saml2AuthorityAttributeLookupImpl.java delete mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/SamlAssertionUtils.java delete mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/SamlConfig.java delete mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/ScimSaml2AuthenticatedPrincipal.java delete mode 100644 src/main/java/stirling/software/SPDF/config/security/saml/SimpleScimMappings.java create mode 100644 src/main/java/stirling/software/SPDF/config/security/saml2/CertificateUtils.java create mode 100644 src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticatedPrincipal.java create mode 100644 src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationFailureHandler.java create mode 100644 src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationSuccessHandler.java create mode 100644 src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2ResponseAuthenticationConverter.java diff --git a/build.gradle b/build.gradle index 022dd5de8ee..18d57a53ebb 100644 --- a/build.gradle +++ b/build.gradle @@ -32,11 +32,9 @@ java { repositories { mavenCentral() maven { url "https://jitpack.io" } - maven { - url "https://build.shibboleth.net/nexus/content/repositories/releases/" - } + maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" } maven { - url "https://build.shibboleth.net/maven/releases/" + url 'https://build.shibboleth.net/maven/releases' } } @@ -135,7 +133,7 @@ dependencies { implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion" implementation 'com.posthog.java:posthog:1.1.1' implementation 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1' - + if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") { implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion" @@ -148,6 +146,14 @@ dependencies { //2.2.x requires rebuild of DB file.. need migration path runtimeOnly "com.h2database:h2:2.1.214" // implementation "com.h2database:h2:2.2.224" + constraints { + implementation "org.opensaml:opensaml-core" + implementation "org.opensaml:opensaml-saml-api" + implementation "org.opensaml:opensaml-saml-impl" + } + implementation "org.springframework.security:spring-security-saml2-service-provider" + + implementation 'com.coveo:saml-client:5.0.0' } testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion" diff --git a/src/main/java/stirling/software/SPDF/SPdfApplication.java b/src/main/java/stirling/software/SPDF/SPdfApplication.java index b184f1ef83b..eddf7306c07 100644 --- a/src/main/java/stirling/software/SPDF/SPdfApplication.java +++ b/src/main/java/stirling/software/SPDF/SPdfApplication.java @@ -33,8 +33,12 @@ public class SPdfApplication { @Autowired private Environment env; @Autowired ApplicationProperties applicationProperties; + private static String baseUrlStatic; private static String serverPortStatic; + @Value("${baseUrl:http://localhost}") + private String baseUrl; + @Value("${server.port:8080}") public void setServerPortStatic(String port) { if ("auto".equalsIgnoreCase(port)) { @@ -65,12 +69,13 @@ private static boolean isPortAvailable(int port) { @PostConstruct public void init() { + baseUrlStatic = this.baseUrl; // Check if the BROWSER_OPEN environment variable is set to true String browserOpenEnv = env.getProperty("BROWSER_OPEN"); boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv); if (browserOpen) { try { - String url = "http://localhost:" + getStaticPort(); + String url = baseUrl + ":" + getStaticPort(); String os = System.getProperty("os.name").toLowerCase(); Runtime rt = Runtime.getRuntime(); @@ -138,10 +143,18 @@ public static void main(String[] args) throws IOException, InterruptedException private static void printStartupLogs() { logger.info("Stirling-PDF Started."); - String url = "http://localhost:" + getStaticPort(); + String url = baseUrlStatic + ":" + getStaticPort(); logger.info("Navigate to {}", url); } + public static String getStaticBaseUrl() { + return baseUrlStatic; + } + + public String getNonStaticBaseUrl() { + return baseUrlStatic; + } + public static String getStaticPort() { return serverPortStatic; } diff --git a/src/main/java/stirling/software/SPDF/config/security/CustomLogoutSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/CustomLogoutSuccessHandler.java index d62956e1761..8d5aa76d015 100644 --- a/src/main/java/stirling/software/SPDF/config/security/CustomLogoutSuccessHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/CustomLogoutSuccessHandler.java @@ -1,27 +1,237 @@ package stirling.software.SPDF.config.security; import java.io.IOException; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.util.ArrayList; +import java.util.List; +import org.springframework.core.io.Resource; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; +import com.coveo.saml.SamlClient; + import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import stirling.software.SPDF.SPdfApplication; +import stirling.software.SPDF.config.security.saml2.CertificateUtils; +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal; +import stirling.software.SPDF.model.ApplicationProperties; +import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; +import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2; +import stirling.software.SPDF.model.Provider; +import stirling.software.SPDF.model.provider.UnsupportedProviderException; +import stirling.software.SPDF.utils.UrlUtils; +@Slf4j +@AllArgsConstructor public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { + private final ApplicationProperties applicationProperties; + @Override public void onLogoutSuccess( HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - if (request.getParameter("userIsDisabled") != null) { - getRedirectStrategy() - .sendRedirect(request, response, "/login?erroroauth=userIsDisabled"); - return; + if (!response.isCommitted()) { + // Handle user logout due to disabled account + if (request.getParameter("userIsDisabled") != null) { + response.sendRedirect( + request.getContextPath() + "/login?erroroauth=userIsDisabled"); + return; + } + // Handle OAuth2 authentication error + if (request.getParameter("oauth2AuthenticationErrorWeb") != null) { + response.sendRedirect( + request.getContextPath() + "/login?erroroauth=userAlreadyExistsWeb"); + return; + } + if (authentication != null) { + // Handle SAML2 logout redirection + if (authentication instanceof Saml2Authentication) { + getRedirect_saml2(request, response, authentication); + return; + } + // Handle OAuth2 logout redirection + else if (authentication instanceof OAuth2AuthenticationToken) { + getRedirect_oauth2(request, response, authentication); + return; + } + // Handle Username/Password logout + else if (authentication instanceof UsernamePasswordAuthenticationToken) { + getRedirectStrategy().sendRedirect(request, response, "/login?logout=true"); + return; + } + // Handle unknown authentication types + else { + log.error( + "authentication class unknown: " + + authentication.getClass().getSimpleName()); + getRedirectStrategy().sendRedirect(request, response, "/login?logout=true"); + return; + } + } else { + // Redirect to login page after logout + getRedirectStrategy().sendRedirect(request, response, "/login?logout=true"); + return; + } } + } + + // Redirect for SAML2 authentication logout + private void getRedirect_saml2( + HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException { + + SAML2 samlConf = applicationProperties.getSecurity().getSaml2(); + String registrationId = samlConf.getRegistrationId(); + + Saml2Authentication samlAuthentication = (Saml2Authentication) authentication; + CustomSaml2AuthenticatedPrincipal principal = + (CustomSaml2AuthenticatedPrincipal) samlAuthentication.getPrincipal(); + + String nameIdValue = principal.getName(); + + try { + // Read certificate from the resource + Resource certificateResource = samlConf.getSpCert(); + X509Certificate certificate = CertificateUtils.readCertificate(certificateResource); + + List certificates = new ArrayList<>(); + certificates.add(certificate); + + // Construct URLs required for SAML configuration + String serverUrl = + SPdfApplication.getStaticBaseUrl() + ":" + SPdfApplication.getStaticPort(); + + String relyingPartyIdentifier = + serverUrl + "/saml2/service-provider-metadata/" + registrationId; + + String assertionConsumerServiceUrl = serverUrl + "/login/saml2/sso/" + registrationId; + + String idpUrl = samlConf.getIdpSingleLogoutUrl(); + + String idpIssuer = samlConf.getIdpIssuer(); + + // Create SamlClient instance for SAML logout + SamlClient samlClient = + new SamlClient( + relyingPartyIdentifier, + assertionConsumerServiceUrl, + idpUrl, + idpIssuer, + certificates, + SamlClient.SamlIdpBinding.POST); + + // Read private key for service provider + Resource privateKeyResource = samlConf.getPrivateKey(); + RSAPrivateKey privateKey = CertificateUtils.readPrivateKey(privateKeyResource); + + // Set service provider keys for the SamlClient + samlClient.setSPKeys(certificate, privateKey); + + // Redirect to identity provider for logout + samlClient.redirectToIdentityProvider(response, null, nameIdValue); + } catch (Exception e) { + log.error(nameIdValue, e); + getRedirectStrategy().sendRedirect(request, response, "/login?logout=true"); + } + } + + // Redirect for OAuth2 authentication logout + private void getRedirect_oauth2( + HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException { + String param = "logout=true"; + String registrationId = null; + String issuer = null; + String clientId = null; + OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); + + if (authentication instanceof OAuth2AuthenticationToken) { + OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication; + registrationId = oauthToken.getAuthorizedClientRegistrationId(); + + try { + // Get OAuth2 provider details from configuration + Provider provider = oauth.getClient().get(registrationId); + issuer = provider.getIssuer(); + clientId = provider.getClientId(); + } catch (UnsupportedProviderException e) { + log.error(e.getMessage()); + } + } else { + registrationId = oauth.getProvider() != null ? oauth.getProvider() : ""; + issuer = oauth.getIssuer(); + clientId = oauth.getClientId(); + } + String errorMessage = ""; + // Handle different error scenarios during logout + if (request.getParameter("oauth2AuthenticationErrorWeb") != null) { + param = "erroroauth=oauth2AuthenticationErrorWeb"; + } else if ((errorMessage = request.getParameter("error")) != null) { + param = "error=" + sanitizeInput(errorMessage); + } else if ((errorMessage = request.getParameter("erroroauth")) != null) { + param = "erroroauth=" + sanitizeInput(errorMessage); + } else if (request.getParameter("oauth2AutoCreateDisabled") != null) { + param = "error=oauth2AutoCreateDisabled"; + } else if (request.getParameter("oauth2_admin_blocked_user") != null) { + param = "erroroauth=oauth2_admin_blocked_user"; + } else if (request.getParameter("userIsDisabled") != null) { + param = "erroroauth=userIsDisabled"; + } else if (request.getParameter("badcredentials") != null) { + param = "error=badcredentials"; + } + + String redirect_url = UrlUtils.getOrigin(request) + "/login?" + param; + + // Redirect based on OAuth2 provider + switch (registrationId.toLowerCase()) { + case "keycloak": + // Add Keycloak specific logout URL if needed + String logoutUrl = + issuer + + "/protocol/openid-connect/logout" + + "?client_id=" + + clientId + + "&post_logout_redirect_uri=" + + response.encodeRedirectURL(redirect_url); + log.info("Redirecting to Keycloak logout URL: " + logoutUrl); + response.sendRedirect(logoutUrl); + break; + case "github": + // Add GitHub specific logout URL if needed + String githubLogoutUrl = "https://github.com/logout"; + log.info("Redirecting to GitHub logout URL: " + githubLogoutUrl); + response.sendRedirect(githubLogoutUrl); + break; + case "google": + // Add Google specific logout URL if needed + // String googleLogoutUrl = + // "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue=" + // + response.encodeRedirectURL(redirect_url); + log.info("Google does not have a specific logout URL"); + // log.info("Redirecting to Google logout URL: " + googleLogoutUrl); + // response.sendRedirect(googleLogoutUrl); + // break; + default: + String defaultRedirectUrl = request.getContextPath() + "/login?" + param; + log.info("Redirecting to default logout URL: " + defaultRedirectUrl); + response.sendRedirect(defaultRedirectUrl); + break; + } + } - getRedirectStrategy().sendRedirect(request, response, "/login?logout=true"); + // Sanitize input to avoid potential security vulnerabilities + private String sanitizeInput(String input) { + return input.replaceAll("[^a-zA-Z0-9 ]", ""); } } diff --git a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java index 79e40e2bb7f..cc2eea45885 100644 --- a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java @@ -1,22 +1,36 @@ package stirling.software.SPDF.config.security; +import java.security.cert.X509Certificate; +import java.util.*; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; -import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.core.io.Resource; import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.ClientRegistrations; +import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; +import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; +import org.springframework.security.saml2.core.Saml2X509Credential; +import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType; import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider; +import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter; import org.springframework.security.web.SecurityFilterChain; @@ -28,13 +42,20 @@ import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler; import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler; -import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler; import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService; -import stirling.software.SPDF.config.security.saml.ConvertResponseToAuthentication; -import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationFailureHandler; -import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationSuccessHandler; +import stirling.software.SPDF.config.security.saml2.CertificateUtils; +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationFailureHandler; +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationSuccessHandler; +import stirling.software.SPDF.config.security.saml2.CustomSaml2ResponseAuthenticationConverter; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.model.ApplicationProperties; +import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; +import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client; +import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2; +import stirling.software.SPDF.model.User; +import stirling.software.SPDF.model.provider.GithubProvider; +import stirling.software.SPDF.model.provider.GoogleProvider; +import stirling.software.SPDF.model.provider.KeycloakProvider; import stirling.software.SPDF.repository.JPATokenRepositoryImpl; @Configuration @@ -45,12 +66,6 @@ public class SecurityConfiguration { @Autowired private CustomUserDetailsService userDetailsService; - @Autowired(required = false) - private GrantedAuthoritiesMapper userAuthoritiesMapper; - - @Autowired(required = false) - private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository; - @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -71,11 +86,8 @@ public PasswordEncoder passwordEncoder() { @Autowired private FirstLoginFilter firstLoginFilter; @Autowired private SessionPersistentRegistry sessionRegistry; - @Autowired private ConvertResponseToAuthentication convertResponseToAuthentication; - @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.authenticationManager(authenticationManager(http)); if (loginEnabledValue) { http.addFilterBefore( @@ -94,128 +106,116 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionRegistry(sessionRegistry) .expiredUrl("/login?logout=true")); - http.formLogin( - formLogin -> - formLogin - .loginPage("/login") - .successHandler( - new CustomAuthenticationSuccessHandler( - loginAttemptService, userService)) - .defaultSuccessUrl("/") - .failureHandler( - new CustomAuthenticationFailureHandler( - loginAttemptService, userService)) - .permitAll()) - .requestCache(requestCache -> requestCache.requestCache(new NullRequestCache())) - .logout( - logout -> - logout.logoutRequestMatcher( - new AntPathRequestMatcher("/logout")) - .logoutSuccessHandler(new CustomLogoutSuccessHandler()) - .invalidateHttpSession(true) // Invalidate session - .deleteCookies("JSESSIONID", "remember-me")) - .rememberMe( - rememberMeConfigurer -> - rememberMeConfigurer // Use the configurator directly - .key("uniqueAndSecret") - .tokenRepository(persistentTokenRepository()) - .tokenValiditySeconds(1209600) // 2 weeks - ) - .authorizeHttpRequests( - authz -> - authz.requestMatchers( - req -> { - String uri = req.getRequestURI(); - String contextPath = req.getContextPath(); - - // Remove the context path from the URI - String trimmedUri = - uri.startsWith(contextPath) - ? uri.substring( - contextPath - .length()) - : uri; - - return trimmedUri.startsWith("/login") - || trimmedUri.startsWith("/oauth") - || trimmedUri.startsWith("/saml2") - || trimmedUri.endsWith(".svg") - || trimmedUri.startsWith( - "/register") - || trimmedUri.startsWith("/error") - || trimmedUri.startsWith("/images/") - || trimmedUri.startsWith("/public/") - || trimmedUri.startsWith("/css/") - || trimmedUri.startsWith("/fonts/") - || trimmedUri.startsWith("/js/") - || trimmedUri.startsWith( - "/api/v1/info/status"); - }) - .permitAll() - .anyRequest() - .authenticated()); + http.authenticationProvider(daoAuthenticationProvider()); + http.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache())); + http.logout( + logout -> + logout.logoutRequestMatcher(new AntPathRequestMatcher("/logout")) + .logoutSuccessHandler( + new CustomLogoutSuccessHandler(applicationProperties)) + .invalidateHttpSession(true) // Invalidate session + .deleteCookies("JSESSIONID", "remember-me")); + http.rememberMe( + rememberMeConfigurer -> + rememberMeConfigurer // Use the configurator directly + .key("uniqueAndSecret") + .tokenRepository(persistentTokenRepository()) + .tokenValiditySeconds(1209600) // 2 weeks + ); + http.authorizeHttpRequests( + authz -> + authz.requestMatchers( + req -> { + String uri = req.getRequestURI(); + String contextPath = req.getContextPath(); + + // Remove the context path from the URI + String trimmedUri = + uri.startsWith(contextPath) + ? uri.substring( + contextPath.length()) + : uri; + + return trimmedUri.startsWith("/login") + || trimmedUri.startsWith("/oauth") + || trimmedUri.startsWith("/saml2") + || trimmedUri.endsWith(".svg") + || trimmedUri.startsWith("/register") + || trimmedUri.startsWith("/error") + || trimmedUri.startsWith("/images/") + || trimmedUri.startsWith("/public/") + || trimmedUri.startsWith("/css/") + || trimmedUri.startsWith("/fonts/") + || trimmedUri.startsWith("/js/") + || trimmedUri.startsWith( + "/api/v1/info/status"); + }) + .permitAll() + .anyRequest() + .authenticated()); + + // Handle User/Password Logins + if (applicationProperties.getSecurity().isUserPass()) { + http.formLogin( + formLogin -> + formLogin + .loginPage("/login") + .successHandler( + new CustomAuthenticationSuccessHandler( + loginAttemptService, userService)) + .failureHandler( + new CustomAuthenticationFailureHandler( + loginAttemptService, userService)) + .defaultSuccessUrl("/") + .permitAll()); + } // Handle OAUTH2 Logins - if (applicationProperties.getSecurity().getOauth2() != null - && applicationProperties.getSecurity().getOauth2().getEnabled() - && !applicationProperties - .getSecurity() - .getLoginMethod() - .equalsIgnoreCase("normal")) { + if (applicationProperties.getSecurity().isOauth2Activ()) { http.oauth2Login( - oauth2 -> - oauth2.loginPage("/oauth2") - /* - This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database. - If user exists, login proceeds as usual. If user does not exist, then it is autocreated but only if 'OAUTH2AutoCreateUser' - is set as true, else login fails with an error message advising the same. - */ + oauth2 -> + oauth2.loginPage("/oauth2") + /* + This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database. + If user exists, login proceeds as usual. If user does not exist, then it is autocreated but only if 'OAUTH2AutoCreateUser' + is set as true, else login fails with an error message advising the same. + */ + .successHandler( + new CustomOAuth2AuthenticationSuccessHandler( + loginAttemptService, + applicationProperties, + userService)) + .failureHandler( + new CustomOAuth2AuthenticationFailureHandler()) + // Add existing Authorities from the database + .userInfoEndpoint( + userInfoEndpoint -> + userInfoEndpoint + .oidcUserService( + new CustomOAuth2UserService( + applicationProperties, + userService, + loginAttemptService)) + .userAuthoritiesMapper( + userAuthoritiesMapper())) + .permitAll()); + } + + // Handle SAML + if (applicationProperties.getSecurity().isSaml2Activ()) { + http.authenticationProvider(samlAuthenticationProvider()); + http.saml2Login( + saml2 -> + saml2.loginPage("/saml2") .successHandler( - new CustomOAuth2AuthenticationSuccessHandler( + new CustomSaml2AuthenticationSuccessHandler( loginAttemptService, applicationProperties, userService)) .failureHandler( - new CustomOAuth2AuthenticationFailureHandler()) - // Add existing Authorities from the database - .userInfoEndpoint( - userInfoEndpoint -> - userInfoEndpoint - .oidcUserService( - new CustomOAuth2UserService( - applicationProperties, - userService, - loginAttemptService)) - .userAuthoritiesMapper( - userAuthoritiesMapper))) - .logout( - logout -> - logout.logoutSuccessHandler( - new CustomOAuth2LogoutSuccessHandler( - applicationProperties))); - } - - // Handle SAML - if (applicationProperties.getSecurity().getSaml() != null - && applicationProperties.getSecurity().getSaml().getEnabled() - && !applicationProperties - .getSecurity() - .getLoginMethod() - .equalsIgnoreCase("normal")) { - http.saml2Login( - saml2 -> { - saml2.loginPage("/saml2") - .relyingPartyRegistrationRepository( - relyingPartyRegistrationRepository) - .successHandler( - new CustomSAMLAuthenticationSuccessHandler( - loginAttemptService, - userService, - applicationProperties)) - .failureHandler( - new CustomSAMLAuthenticationFailureHandler()); - }) + new CustomSaml2AuthenticationFailureHandler()) + .permitAll()) .addFilterBefore( userAuthenticationFilter, Saml2WebSsoAuthenticationFilter.class); } @@ -231,39 +231,234 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @Bean @ConditionalOnProperty( - name = "security.saml.enabled", + name = "security.saml2.enabled", havingValue = "true", matchIfMissing = false) public AuthenticationProvider samlAuthenticationProvider() { OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider(); - authenticationProvider.setResponseAuthenticationConverter(convertResponseToAuthentication); + authenticationProvider.setResponseAuthenticationConverter( + new CustomSaml2ResponseAuthenticationConverter(userService)); return authenticationProvider; } - // @Bean - // public AuthenticationProvider daoAuthenticationProvider() { - // DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); - // provider.setUserDetailsService(userDetailsService); // UserDetailsService - // provider.setPasswordEncoder(passwordEncoder()); // PasswordEncoder - // return provider; - // } - + // Client Registration Repository for OAUTH2 OIDC Login @Bean - public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { - AuthenticationManagerBuilder authenticationManagerBuilder = - http.getSharedObject(AuthenticationManagerBuilder.class); - - // authenticationManagerBuilder = - // authenticationManagerBuilder.authenticationProvider( - // daoAuthenticationProvider()); // Benutzername/Passwort - - if (applicationProperties.getSecurity().getSaml() != null - && applicationProperties.getSecurity().getSaml().getEnabled()) { - authenticationManagerBuilder.authenticationProvider( - samlAuthenticationProvider()); // SAML + @ConditionalOnProperty( + value = "security.oauth2.enabled", + havingValue = "true", + matchIfMissing = false) + public ClientRegistrationRepository clientRegistrationRepository() { + List registrations = new ArrayList<>(); + + githubClientRegistration().ifPresent(registrations::add); + oidcClientRegistration().ifPresent(registrations::add); + googleClientRegistration().ifPresent(registrations::add); + keycloakClientRegistration().ifPresent(registrations::add); + + if (registrations.isEmpty()) { + log.error("At least one OAuth2 provider must be configured"); + System.exit(1); + } + + return new InMemoryClientRegistrationRepository(registrations); + } + + private Optional googleClientRegistration() { + OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); + if (oauth == null || !oauth.getEnabled()) { + return Optional.empty(); + } + Client client = oauth.getClient(); + if (client == null) { + return Optional.empty(); + } + GoogleProvider google = client.getGoogle(); + return google != null && google.isSettingsValid() + ? Optional.of( + ClientRegistration.withRegistrationId(google.getName()) + .clientId(google.getClientId()) + .clientSecret(google.getClientSecret()) + .scope(google.getScopes()) + .authorizationUri(google.getAuthorizationuri()) + .tokenUri(google.getTokenuri()) + .userInfoUri(google.getUserinfouri()) + .userNameAttributeName(google.getUseAsUsername()) + .clientName(google.getClientName()) + .redirectUri("{baseUrl}/login/oauth2/code/" + google.getName()) + .authorizationGrantType( + org.springframework.security.oauth2.core + .AuthorizationGrantType.AUTHORIZATION_CODE) + .build()) + : Optional.empty(); + } + + private Optional keycloakClientRegistration() { + OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); + if (oauth == null || !oauth.getEnabled()) { + return Optional.empty(); + } + Client client = oauth.getClient(); + if (client == null) { + return Optional.empty(); } - return authenticationManagerBuilder.build(); + KeycloakProvider keycloak = client.getKeycloak(); + + return keycloak != null && keycloak.isSettingsValid() + ? Optional.of( + ClientRegistrations.fromIssuerLocation(keycloak.getIssuer()) + .registrationId(keycloak.getName()) + .clientId(keycloak.getClientId()) + .clientSecret(keycloak.getClientSecret()) + .scope(keycloak.getScopes()) + .userNameAttributeName(keycloak.getUseAsUsername()) + .clientName(keycloak.getClientName()) + .build()) + : Optional.empty(); + } + + private Optional githubClientRegistration() { + + OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); + if (oauth == null || !oauth.getEnabled()) { + return Optional.empty(); + } + Client client = oauth.getClient(); + if (client == null) { + return Optional.empty(); + } + GithubProvider github = client.getGithub(); + return github != null && github.isSettingsValid() + ? Optional.of( + ClientRegistration.withRegistrationId(github.getName()) + .clientId(github.getClientId()) + .clientSecret(github.getClientSecret()) + .scope(github.getScopes()) + .authorizationUri(github.getAuthorizationuri()) + .tokenUri(github.getTokenuri()) + .userInfoUri(github.getUserinfouri()) + .userNameAttributeName(github.getUseAsUsername()) + .clientName(github.getClientName()) + .redirectUri("{baseUrl}/login/oauth2/code/" + github.getName()) + .authorizationGrantType( + org.springframework.security.oauth2.core + .AuthorizationGrantType.AUTHORIZATION_CODE) + .build()) + : Optional.empty(); + } + + private Optional oidcClientRegistration() { + OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); + if (oauth == null + || oauth.getIssuer() == null + || oauth.getIssuer().isEmpty() + || oauth.getClientId() == null + || oauth.getClientId().isEmpty() + || oauth.getClientSecret() == null + || oauth.getClientSecret().isEmpty() + || oauth.getScopes() == null + || oauth.getScopes().isEmpty() + || oauth.getUseAsUsername() == null + || oauth.getUseAsUsername().isEmpty()) { + return Optional.empty(); + } + return Optional.of( + ClientRegistrations.fromIssuerLocation(oauth.getIssuer()) + .registrationId("oidc") + .clientId(oauth.getClientId()) + .clientSecret(oauth.getClientSecret()) + .scope(oauth.getScopes()) + .userNameAttributeName(oauth.getUseAsUsername()) + .clientName("OIDC") + .build()); + } + + @Bean + @ConditionalOnProperty( + name = "security.saml2.enabled", + havingValue = "true", + matchIfMissing = false) + public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception { + + SAML2 samlConf = applicationProperties.getSecurity().getSaml2(); + + Resource privateKeyResource = samlConf.getPrivateKey(); + + Resource certificateResource = samlConf.getSpCert(); + + Saml2X509Credential signingCredential = + new Saml2X509Credential( + CertificateUtils.readPrivateKey(privateKeyResource), + CertificateUtils.readCertificate(certificateResource), + Saml2X509CredentialType.SIGNING); + + X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getidpCert()); + + Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert); + + RelyingPartyRegistration rp = + RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId()) + .signingX509Credentials((c) -> c.add(signingCredential)) + .assertingPartyDetails( + (details) -> + details.entityId(samlConf.getIdpIssuer()) + .singleSignOnServiceLocation( + samlConf.getIdpSingleLoginUrl()) + .verificationX509Credentials( + (c) -> c.add(verificationCredential)) + .wantAuthnRequestsSigned(true)) + .build(); + return new InMemoryRelyingPartyRegistrationRepository(rp); + } + + @Bean + public DaoAuthenticationProvider daoAuthenticationProvider() { + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setUserDetailsService(userDetailsService); + provider.setPasswordEncoder(passwordEncoder()); + return provider; + } + + /* + This following function is to grant Authorities to the OAUTH2 user from the values stored in the database. + This is required for the internal; 'hasRole()' function to give out the correct role. + */ + @Bean + @ConditionalOnProperty( + value = "security.oauth2.enabled", + havingValue = "true", + matchIfMissing = false) + GrantedAuthoritiesMapper userAuthoritiesMapper() { + return (authorities) -> { + Set mappedAuthorities = new HashSet<>(); + + authorities.forEach( + authority -> { + // Add existing OAUTH2 Authorities + mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority())); + + // Add Authorities from database for existing user, if user is present. + if (authority instanceof OAuth2UserAuthority oauth2Auth) { + String useAsUsername = + applicationProperties + .getSecurity() + .getOauth2() + .getUseAsUsername(); + Optional userOpt = + userService.findByUsernameIgnoreCase( + (String) oauth2Auth.getAttributes().get(useAsUsername)); + if (userOpt.isPresent()) { + User user = userOpt.get(); + if (user != null) { + mappedAuthorities.add( + new SimpleGrantedAuthority( + userService.findRole(user).getAuthority())); + } + } + } + }); + return mappedAuthorities; + }; } @Bean diff --git a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java index 7b5bb47a843..4b62f6d2992 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserAuthenticationFilter.java @@ -22,6 +22,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.model.ApiKeyAuthenticationToken; import stirling.software.SPDF.model.User; @@ -111,7 +112,9 @@ protected void doFilterInternal( response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter() .write( - "Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternatively you can disable authentication if this is unexpected"); + "Authentication required. Please provide a X-API-KEY in request header.\n" + + "This is found in Settings -> Account Settings -> API Key\n" + + "Alternatively you can disable authentication if this is unexpected"); return; } } @@ -124,6 +127,8 @@ protected void doFilterInternal( username = ((UserDetails) principal).getUsername(); } else if (principal instanceof OAuth2User) { username = ((OAuth2User) principal).getName(); + } else if (principal instanceof CustomSaml2AuthenticatedPrincipal) { + username = ((CustomSaml2AuthenticatedPrincipal) principal).getName(); } else if (principal instanceof String) { username = (String) principal; } diff --git a/src/main/java/stirling/software/SPDF/config/security/UserService.java b/src/main/java/stirling/software/SPDF/config/security/UserService.java index 27e0baa2ec3..bb78989f721 100644 --- a/src/main/java/stirling/software/SPDF/config/security/UserService.java +++ b/src/main/java/stirling/software/SPDF/config/security/UserService.java @@ -20,6 +20,7 @@ import org.springframework.stereotype.Service; import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface; +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface; import stirling.software.SPDF.model.AuthenticationType; @@ -338,6 +339,10 @@ public void invalidateUserSessions(String username) { } else if (principal instanceof OAuth2User) { OAuth2User oAuth2User = (OAuth2User) principal; usernameP = oAuth2User.getName(); + } else if (principal instanceof CustomSaml2AuthenticatedPrincipal) { + CustomSaml2AuthenticatedPrincipal saml2User = + (CustomSaml2AuthenticatedPrincipal) principal; + usernameP = saml2User.getName(); } else if (principal instanceof String) { usernameP = (String) principal; } diff --git a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationFailureHandler.java b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationFailureHandler.java index ebbe08f4ec9..88905f76415 100644 --- a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationFailureHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationFailureHandler.java @@ -51,8 +51,7 @@ public void onAuthenticationFailure( } log.error("OAuth2 Authentication error: " + errorCode); log.error("OAuth2AuthenticationException", exception); - getRedirectStrategy() - .sendRedirect(request, response, "/logout?erroroauth=" + errorCode); + getRedirectStrategy().sendRedirect(request, response, "/login?erroroauth=" + errorCode); return; } log.error("Unhandled authentication exception", exception); diff --git a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java index c8c3f2174d4..36c4cfb366c 100644 --- a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java @@ -75,6 +75,11 @@ public void onAuthenticationSuccess( throw new LockedException( "Your account has been locked due to too many failed login attempts."); } + if (userService.isUserDisabled(username)) { + getRedirectStrategy() + .sendRedirect(request, response, "/logout?userIsDisabled=true"); + return; + } if (userService.usernameExistsIgnoreCase(username) && userService.hasPassword(username) && !userService.isAuthenticationTypeByUsername( diff --git a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2LogoutSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2LogoutSuccessHandler.java deleted file mode 100644 index 5bbff53f623..00000000000 --- a/src/main/java/stirling/software/SPDF/config/security/oauth2/CustomOAuth2LogoutSuccessHandler.java +++ /dev/null @@ -1,122 +0,0 @@ -package stirling.software.SPDF.config.security.oauth2; - -import java.io.IOException; - -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import stirling.software.SPDF.model.ApplicationProperties; -import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; -import stirling.software.SPDF.model.Provider; -import stirling.software.SPDF.model.provider.UnsupportedProviderException; -import stirling.software.SPDF.utils.UrlUtils; - -@Slf4j -public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { - - private final ApplicationProperties applicationProperties; - - public CustomOAuth2LogoutSuccessHandler(ApplicationProperties applicationProperties) { - this.applicationProperties = applicationProperties; - } - - @Override - public void onLogoutSuccess( - HttpServletRequest request, HttpServletResponse response, Authentication authentication) - throws IOException, ServletException { - String param = "logout=true"; - String registrationId = null; - String issuer = null; - String clientId = null; - - if (authentication == null) { - if (request.getParameter("userIsDisabled") != null) { - response.sendRedirect( - request.getContextPath() + "/login?erroroauth=userIsDisabled"); - } else { - super.onLogoutSuccess(request, response, authentication); - } - return; - } - OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); - - if (authentication instanceof OAuth2AuthenticationToken) { - OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication; - registrationId = oauthToken.getAuthorizedClientRegistrationId(); - - try { - Provider provider = oauth.getClient().get(registrationId); - issuer = provider.getIssuer(); - clientId = provider.getClientId(); - } catch (UnsupportedProviderException e) { - log.error(e.getMessage()); - } - } else { - registrationId = oauth.getProvider() != null ? oauth.getProvider() : ""; - issuer = oauth.getIssuer(); - clientId = oauth.getClientId(); - } - String errorMessage = ""; - if (request.getParameter("oauth2AuthenticationErrorWeb") != null) { - param = "erroroauth=oauth2AuthenticationErrorWeb"; - } else if ((errorMessage = request.getParameter("error")) != null) { - param = "error=" + sanitizeInput(errorMessage); - } else if ((errorMessage = request.getParameter("erroroauth")) != null) { - param = "erroroauth=" + sanitizeInput(errorMessage); - } else if (request.getParameter("oauth2AutoCreateDisabled") != null) { - param = "error=oauth2AutoCreateDisabled"; - } else if (request.getParameter("oauth2_admin_blocked_user") != null) { - param = "erroroauth=oauth2_admin_blocked_user"; - } else if (request.getParameter("userIsDisabled") != null) { - param = "erroroauth=userIsDisabled"; - } else if (request.getParameter("badcredentials") != null) { - param = "error=badcredentials"; - } - - String redirect_url = UrlUtils.getOrigin(request) + "/login?" + param; - - switch (registrationId.toLowerCase()) { - case "keycloak": - // Add Keycloak specific logout URL if needed - String logoutUrl = - issuer - + "/protocol/openid-connect/logout" - + "?client_id=" - + clientId - + "&post_logout_redirect_uri=" - + response.encodeRedirectURL(redirect_url); - log.info("Redirecting to Keycloak logout URL: " + logoutUrl); - response.sendRedirect(logoutUrl); - break; - case "github": - // Add GitHub specific logout URL if needed - String githubLogoutUrl = "https://github.com/logout"; - log.info("Redirecting to GitHub logout URL: " + githubLogoutUrl); - response.sendRedirect(githubLogoutUrl); - break; - case "google": - // Add Google specific logout URL if needed - // String googleLogoutUrl = - // "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue=" - // + response.encodeRedirectURL(redirect_url); - log.info("Google does not have a specific logout URL"); - // log.info("Redirecting to Google logout URL: " + googleLogoutUrl); - // response.sendRedirect(googleLogoutUrl); - // break; - default: - String defaultRedirectUrl = request.getContextPath() + "/login?" + param; - log.info("Redirecting to default logout URL: " + defaultRedirectUrl); - response.sendRedirect(defaultRedirectUrl); - break; - } - } - - private String sanitizeInput(String input) { - return input.replaceAll("[^a-zA-Z0-9 ]", ""); - } -} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/ConvertResponseToAuthentication.java b/src/main/java/stirling/software/SPDF/config/security/saml/ConvertResponseToAuthentication.java deleted file mode 100644 index 02bf0593078..00000000000 --- a/src/main/java/stirling/software/SPDF/config/security/saml/ConvertResponseToAuthentication.java +++ /dev/null @@ -1,68 +0,0 @@ -package stirling.software.SPDF.config.security.saml; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.opensaml.saml.saml2.core.Assertion; -import org.springframework.core.convert.converter.Converter; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.stereotype.Component; -import org.springframework.util.CollectionUtils; - -import lombok.extern.slf4j.Slf4j; - -@Component -@Slf4j -public class ConvertResponseToAuthentication - implements Converter { - - private final Saml2AuthorityAttributeLookup saml2AuthorityAttributeLookup; - - public ConvertResponseToAuthentication( - Saml2AuthorityAttributeLookup saml2AuthorityAttributeLookup) { - this.saml2AuthorityAttributeLookup = saml2AuthorityAttributeLookup; - } - - @Override - public Saml2Authentication convert(ResponseToken responseToken) { - final Assertion assertion = - CollectionUtils.firstElement(responseToken.getResponse().getAssertions()); - final Map> attributes = - SamlAssertionUtils.getAssertionAttributes(assertion); - final String registrationId = - responseToken.getToken().getRelyingPartyRegistration().getRegistrationId(); - final ScimSaml2AuthenticatedPrincipal principal = - new ScimSaml2AuthenticatedPrincipal( - assertion, - attributes, - saml2AuthorityAttributeLookup.getIdentityMappings(registrationId)); - final Collection assertionAuthorities = - getAssertionAuthorities( - attributes, - saml2AuthorityAttributeLookup.getAuthorityAttribute(registrationId)); - return new Saml2Authentication( - principal, responseToken.getToken().getSaml2Response(), assertionAuthorities); - } - - private static Collection getAssertionAuthorities( - final Map> attributes, final String authoritiesAttributeName) { - if (attributes == null || attributes.isEmpty()) { - return Collections.emptySet(); - } - - final List groups = new ArrayList<>(attributes.get(authoritiesAttributeName)); - return groups.stream() - .filter(String.class::isInstance) - .map(String.class::cast) - .map(String::toLowerCase) - .map(SimpleGrantedAuthority::new) - .collect(Collectors.toSet()); - } -} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/CustomSAMLAuthenticationFailureHandler.java b/src/main/java/stirling/software/SPDF/config/security/saml/CustomSAMLAuthenticationFailureHandler.java deleted file mode 100644 index 2a61e771f4d..00000000000 --- a/src/main/java/stirling/software/SPDF/config/security/saml/CustomSAMLAuthenticationFailureHandler.java +++ /dev/null @@ -1,51 +0,0 @@ -package stirling.software.SPDF.config.security.saml; - -import java.io.IOException; - -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.DisabledException; -import org.springframework.security.authentication.LockedException; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class CustomSAMLAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { - - @Override - public void onAuthenticationFailure( - HttpServletRequest request, - HttpServletResponse response, - AuthenticationException exception) - throws IOException, ServletException { - - if (exception instanceof BadCredentialsException) { - log.error("BadCredentialsException", exception); - getRedirectStrategy().sendRedirect(request, response, "/login?error=badcredentials"); - return; - } - if (exception instanceof DisabledException) { - log.error("User is deactivated: ", exception); - getRedirectStrategy().sendRedirect(request, response, "/logout?userIsDisabled=true"); - return; - } - if (exception instanceof LockedException) { - log.error("Account locked: ", exception); - getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked"); - return; - } - if (exception instanceof Saml2AuthenticationException) { - log.error("SAML2 Authentication error: ", exception); - getRedirectStrategy() - .sendRedirect(request, response, "/logout?error=saml2AuthenticationError"); - return; - } - log.error("Unhandled authentication exception", exception); - super.onAuthenticationFailure(request, response, exception); - } -} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/CustomSAMLAuthenticationSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/saml/CustomSAMLAuthenticationSuccessHandler.java deleted file mode 100644 index 46d20ac02eb..00000000000 --- a/src/main/java/stirling/software/SPDF/config/security/saml/CustomSAMLAuthenticationSuccessHandler.java +++ /dev/null @@ -1,108 +0,0 @@ -package stirling.software.SPDF.config.security.saml; - -import java.io.IOException; - -import org.springframework.security.authentication.LockedException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; -import org.springframework.security.web.savedrequest.SavedRequest; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; -import lombok.extern.slf4j.Slf4j; -import stirling.software.SPDF.config.security.LoginAttemptService; -import stirling.software.SPDF.config.security.UserService; -import stirling.software.SPDF.model.ApplicationProperties; -import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; -import stirling.software.SPDF.model.AuthenticationType; -import stirling.software.SPDF.utils.RequestUriUtils; - -@Slf4j -public class CustomSAMLAuthenticationSuccessHandler - extends SavedRequestAwareAuthenticationSuccessHandler { - - private LoginAttemptService loginAttemptService; - private UserService userService; - private ApplicationProperties applicationProperties; - - public CustomSAMLAuthenticationSuccessHandler( - LoginAttemptService loginAttemptService, - UserService userService, - ApplicationProperties applicationProperties) { - this.loginAttemptService = loginAttemptService; - this.userService = userService; - this.applicationProperties = applicationProperties; - } - - @Override - public void onAuthenticationSuccess( - HttpServletRequest request, HttpServletResponse response, Authentication authentication) - throws ServletException, IOException { - - Object principal = authentication.getPrincipal(); - String username = ""; - - if (principal instanceof OAuth2User) { - OAuth2User oauthUser = (OAuth2User) principal; - username = oauthUser.getName(); - } else if (principal instanceof UserDetails) { - UserDetails oauthUser = (UserDetails) principal; - username = oauthUser.getUsername(); - } else if (principal instanceof ScimSaml2AuthenticatedPrincipal) { - ScimSaml2AuthenticatedPrincipal samlPrincipal = - (ScimSaml2AuthenticatedPrincipal) principal; - username = samlPrincipal.getName(); - } - - // Get the saved request - HttpSession session = request.getSession(false); - String contextPath = request.getContextPath(); - SavedRequest savedRequest = - (session != null) - ? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST") - : null; - - if (savedRequest != null - && !RequestUriUtils.isStaticResource(contextPath, savedRequest.getRedirectUrl())) { - // Redirect to the original destination - super.onAuthenticationSuccess(request, response, authentication); - } else { - OAUTH2 oAuth = applicationProperties.getSecurity().getOauth2(); - - if (loginAttemptService.isBlocked(username)) { - if (session != null) { - session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST"); - } - throw new LockedException( - "Your account has been locked due to too many failed login attempts."); - } - if (userService.usernameExistsIgnoreCase(username) - && userService.hasPassword(username) - && !userService.isAuthenticationTypeByUsername( - username, AuthenticationType.OAUTH2) - && oAuth.getAutoCreateUser()) { - response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true"); - return; - } - try { - if (oAuth.getBlockRegistration() - && !userService.usernameExistsIgnoreCase(username)) { - response.sendRedirect(contextPath + "/logout?oauth2_admin_blocked_user=true"); - return; - } - if (principal instanceof OAuth2User) { - userService.processOAuth2PostLogin(username, oAuth.getAutoCreateUser()); - } - response.sendRedirect(contextPath + "/"); - return; - } catch (IllegalArgumentException e) { - response.sendRedirect(contextPath + "/logout?invalidUsername=true"); - return; - } - } - } -} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/SAMLLogoutSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/saml/SAMLLogoutSuccessHandler.java deleted file mode 100644 index 24e81889fe6..00000000000 --- a/src/main/java/stirling/software/SPDF/config/security/saml/SAMLLogoutSuccessHandler.java +++ /dev/null @@ -1,38 +0,0 @@ -package stirling.software.SPDF.config.security.saml; - -import java.io.IOException; - -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class SAMLLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { - - @Override - public void onLogoutSuccess( - HttpServletRequest request, HttpServletResponse response, Authentication authentication) - throws IOException, ServletException { - - String redirectUrl = determineTargetUrl(request, response, authentication); - - if (response.isCommitted()) { - log.debug("Response has already been committed. Unable to redirect to " + redirectUrl); - return; - } - - getRedirectStrategy().sendRedirect(request, response, redirectUrl); - } - - protected String determineTargetUrl( - HttpServletRequest request, - HttpServletResponse response, - Authentication authentication) { - // Default to the root URL - return "/"; - } -} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/Saml2AuthorityAttributeLookup.java b/src/main/java/stirling/software/SPDF/config/security/saml/Saml2AuthorityAttributeLookup.java deleted file mode 100644 index cceb5bf32c3..00000000000 --- a/src/main/java/stirling/software/SPDF/config/security/saml/Saml2AuthorityAttributeLookup.java +++ /dev/null @@ -1,7 +0,0 @@ -package stirling.software.SPDF.config.security.saml; - -public interface Saml2AuthorityAttributeLookup { - String getAuthorityAttribute(String registrationId); - - SimpleScimMappings getIdentityMappings(String registrationId); -} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/Saml2AuthorityAttributeLookupImpl.java b/src/main/java/stirling/software/SPDF/config/security/saml/Saml2AuthorityAttributeLookupImpl.java deleted file mode 100644 index c3b038e5834..00000000000 --- a/src/main/java/stirling/software/SPDF/config/security/saml/Saml2AuthorityAttributeLookupImpl.java +++ /dev/null @@ -1,17 +0,0 @@ -package stirling.software.SPDF.config.security.saml; - -import org.springframework.stereotype.Component; - -@Component -public class Saml2AuthorityAttributeLookupImpl implements Saml2AuthorityAttributeLookup { - - @Override - public String getAuthorityAttribute(String registrationId) { - return "authorityAttributeName"; - } - - @Override - public SimpleScimMappings getIdentityMappings(String registrationId) { - return new SimpleScimMappings(); - } -} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/SamlAssertionUtils.java b/src/main/java/stirling/software/SPDF/config/security/saml/SamlAssertionUtils.java deleted file mode 100644 index c4a560bf71a..00000000000 --- a/src/main/java/stirling/software/SPDF/config/security/saml/SamlAssertionUtils.java +++ /dev/null @@ -1,63 +0,0 @@ -package stirling.software.SPDF.config.security.saml; - -import java.time.Instant; -import java.util.*; - -import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.schema.*; -import org.opensaml.saml.saml2.core.Assertion; - -public class SamlAssertionUtils { - - public static Map> getAssertionAttributes(Assertion assertion) { - Map> attributeMap = new LinkedHashMap<>(); - - assertion - .getAttributeStatements() - .forEach( - attributeStatement -> { - attributeStatement - .getAttributes() - .forEach( - attribute -> { - List attributeValues = new ArrayList<>(); - - attribute - .getAttributeValues() - .forEach( - xmlObject -> { - Object attributeValue = - getXmlObjectValue( - xmlObject); - if (attributeValue != null) { - attributeValues.add( - attributeValue); - } - }); - - attributeMap.put( - attribute.getName(), attributeValues); - }); - }); - - return attributeMap; - } - - public static Object getXmlObjectValue(XMLObject xmlObject) { - if (xmlObject instanceof XSAny) { - return ((XSAny) xmlObject).getTextContent(); - } else if (xmlObject instanceof XSString) { - return ((XSString) xmlObject).getValue(); - } else if (xmlObject instanceof XSInteger) { - return ((XSInteger) xmlObject).getValue(); - } else if (xmlObject instanceof XSURI) { - return ((XSURI) xmlObject).getURI(); - } else if (xmlObject instanceof XSBoolean) { - return ((XSBoolean) xmlObject).getValue().getValue(); - } else if (xmlObject instanceof XSDateTime) { - Instant dateTime = ((XSDateTime) xmlObject).getValue(); - return (dateTime != null) ? Instant.ofEpochMilli(dateTime.toEpochMilli()) : null; - } - return null; - } -} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/SamlConfig.java b/src/main/java/stirling/software/SPDF/config/security/saml/SamlConfig.java deleted file mode 100644 index 358c448866a..00000000000 --- a/src/main/java/stirling/software/SPDF/config/security/saml/SamlConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -package stirling.software.SPDF.config.security.saml; - -import java.security.cert.CertificateException; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations; - -import lombok.extern.slf4j.Slf4j; -import stirling.software.SPDF.model.ApplicationProperties; - -@Configuration -@Slf4j -public class SamlConfig { - - @Autowired ApplicationProperties applicationProperties; - - @Bean - @ConditionalOnProperty( - value = "security.saml.enabled", - havingValue = "true", - matchIfMissing = false) - public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() - throws CertificateException { - RelyingPartyRegistration registration = - RelyingPartyRegistrations.fromMetadataLocation( - applicationProperties - .getSecurity() - .getSaml() - .getIdpMetadataLocation()) - .entityId(applicationProperties.getSecurity().getSaml().getEntityId()) - .registrationId( - applicationProperties.getSecurity().getSaml().getRegistrationId()) - .build(); - return new InMemoryRelyingPartyRegistrationRepository(registration); - } -} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/ScimSaml2AuthenticatedPrincipal.java b/src/main/java/stirling/software/SPDF/config/security/saml/ScimSaml2AuthenticatedPrincipal.java deleted file mode 100644 index 9be94f389de..00000000000 --- a/src/main/java/stirling/software/SPDF/config/security/saml/ScimSaml2AuthenticatedPrincipal.java +++ /dev/null @@ -1,89 +0,0 @@ -package stirling.software.SPDF.config.security.saml; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.Function; - -import org.opensaml.saml.saml2.core.Assertion; -import org.springframework.security.core.AuthenticatedPrincipal; -import org.springframework.util.Assert; - -import com.unboundid.scim2.common.types.Email; -import com.unboundid.scim2.common.types.Name; -import com.unboundid.scim2.common.types.UserResource; - -public class ScimSaml2AuthenticatedPrincipal implements AuthenticatedPrincipal, Serializable { - - private static final long serialVersionUID = 1L; - - private final transient UserResource userResource; - - public ScimSaml2AuthenticatedPrincipal( - final Assertion assertion, - final Map> attributes, - final SimpleScimMappings attributeMappings) { - Assert.notNull(assertion, "assertion cannot be null"); - Assert.notNull(assertion.getSubject(), "assertion subject cannot be null"); - Assert.notNull( - assertion.getSubject().getNameID(), "assertion subject NameID cannot be null"); - Assert.notNull(attributes, "attributes cannot be null"); - Assert.notNull(attributeMappings, "attributeMappings cannot be null"); - - final Name name = - new Name() - .setFamilyName( - getAttribute( - attributes, - attributeMappings, - SimpleScimMappings::getFamilyName)) - .setGivenName( - getAttribute( - attributes, - attributeMappings, - SimpleScimMappings::getGivenName)); - - final List emails = new ArrayList<>(1); - emails.add( - new Email() - .setValue( - getAttribute( - attributes, - attributeMappings, - SimpleScimMappings::getEmail)) - .setPrimary(true)); - - userResource = - new UserResource() - .setUserName(assertion.getSubject().getNameID().getValue()) - .setName(name) - .setEmails(emails); - } - - private static String getAttribute( - final Map> attributes, - final SimpleScimMappings simpleScimMappings, - final Function attributeMapper) { - - final String key = attributeMapper.apply(simpleScimMappings); - - final List values = attributes.getOrDefault(key, Collections.emptyList()); - - return values.stream() - .filter(String.class::isInstance) - .map(String.class::cast) - .findFirst() - .orElse(null); - } - - @Override - public String getName() { - return this.userResource.getUserName(); - } - - public UserResource getUserResource() { - return this.userResource; - } -} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml/SimpleScimMappings.java b/src/main/java/stirling/software/SPDF/config/security/saml/SimpleScimMappings.java deleted file mode 100644 index 97f61d3158e..00000000000 --- a/src/main/java/stirling/software/SPDF/config/security/saml/SimpleScimMappings.java +++ /dev/null @@ -1,10 +0,0 @@ -package stirling.software.SPDF.config.security.saml; - -import lombok.Data; - -@Data -public class SimpleScimMappings { - String givenName; - String familyName; - String email; -} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml2/CertificateUtils.java b/src/main/java/stirling/software/SPDF/config/security/saml2/CertificateUtils.java new file mode 100644 index 00000000000..6a01ce0aaa9 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/saml2/CertificateUtils.java @@ -0,0 +1,48 @@ +package stirling.software.SPDF.config.security.saml2; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; + +import org.springframework.core.io.Resource; +import org.springframework.util.FileCopyUtils; + +public class CertificateUtils { + + public static X509Certificate readCertificate(Resource certificateResource) throws Exception { + String certificateString = + new String( + FileCopyUtils.copyToByteArray(certificateResource.getInputStream()), + StandardCharsets.UTF_8); + String certContent = + certificateString + .replace("-----BEGIN CERTIFICATE-----", "") + .replace("-----END CERTIFICATE-----", "") + .replaceAll("\\R", "") + .replaceAll("\\s+", ""); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + byte[] decodedCert = Base64.getDecoder().decode(certContent); + return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(decodedCert)); + } + + public static RSAPrivateKey readPrivateKey(Resource privateKeyResource) throws Exception { + String privateKeyString = + new String( + FileCopyUtils.copyToByteArray(privateKeyResource.getInputStream()), + StandardCharsets.UTF_8); + String privateKeyContent = + privateKeyString + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\R", "") + .replaceAll("\\s+", ""); + KeyFactory kf = KeyFactory.getInstance("RSA"); + byte[] decodedKey = Base64.getDecoder().decode(privateKeyContent); + return (RSAPrivateKey) kf.generatePrivate(new PKCS8EncodedKeySpec(decodedKey)); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticatedPrincipal.java b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticatedPrincipal.java new file mode 100644 index 00000000000..efd0dc19917 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticatedPrincipal.java @@ -0,0 +1,45 @@ +package stirling.software.SPDF.config.security.saml2; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; + +public class CustomSaml2AuthenticatedPrincipal + implements Saml2AuthenticatedPrincipal, Serializable { + + private final String name; + private final Map> attributes; + private final String nameId; + private final List sessionIndexes; + + public CustomSaml2AuthenticatedPrincipal( + String name, + Map> attributes, + String nameId, + List sessionIndexes) { + this.name = name; + this.attributes = attributes; + this.nameId = nameId; + this.sessionIndexes = sessionIndexes; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public Map> getAttributes() { + return this.attributes; + } + + public String getNameId() { + return this.nameId; + } + + public List getSessionIndexes() { + return this.sessionIndexes; + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationFailureHandler.java b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationFailureHandler.java new file mode 100644 index 00000000000..3b652d3588b --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationFailureHandler.java @@ -0,0 +1,38 @@ +package stirling.software.SPDF.config.security.saml2; + +import java.io.IOException; + +import org.springframework.security.authentication.ProviderNotFoundException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.saml2.core.Saml2Error; +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { + + @Override + public void onAuthenticationFailure( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException exception) + throws IOException, ServletException { + if (exception instanceof Saml2AuthenticationException) { + Saml2Error error = ((Saml2AuthenticationException) exception).getSaml2Error(); + getRedirectStrategy() + .sendRedirect(request, response, "/login?erroroauth=" + error.getErrorCode()); + } else if (exception instanceof ProviderNotFoundException) { + getRedirectStrategy() + .sendRedirect( + request, + response, + "/login?erroroauth=not_authentication_provider_found"); + } + log.error("AuthenticationException: " + exception); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationSuccessHandler.java new file mode 100644 index 00000000000..d4b917581cf --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2AuthenticationSuccessHandler.java @@ -0,0 +1,91 @@ +package stirling.software.SPDF.config.security.saml2; + +import java.io.IOException; + +import org.springframework.security.authentication.LockedException; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.security.web.savedrequest.SavedRequest; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import lombok.AllArgsConstructor; +import stirling.software.SPDF.config.security.LoginAttemptService; +import stirling.software.SPDF.config.security.UserService; +import stirling.software.SPDF.model.ApplicationProperties; +import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2; +import stirling.software.SPDF.model.AuthenticationType; +import stirling.software.SPDF.utils.RequestUriUtils; + +@AllArgsConstructor +public class CustomSaml2AuthenticationSuccessHandler + extends SavedRequestAwareAuthenticationSuccessHandler { + + private LoginAttemptService loginAttemptService; + + private ApplicationProperties applicationProperties; + private UserService userService; + + @Override + public void onAuthenticationSuccess( + HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws ServletException, IOException { + + Object principal = authentication.getPrincipal(); + + if (principal instanceof CustomSaml2AuthenticatedPrincipal) { + String username = ((CustomSaml2AuthenticatedPrincipal) principal).getName(); + // Get the saved request + HttpSession session = request.getSession(false); + String contextPath = request.getContextPath(); + SavedRequest savedRequest = + (session != null) + ? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST") + : null; + + if (savedRequest != null + && !RequestUriUtils.isStaticResource( + contextPath, savedRequest.getRedirectUrl())) { + // Redirect to the original destination + super.onAuthenticationSuccess(request, response, authentication); + } else { + SAML2 saml2 = applicationProperties.getSecurity().getSaml2(); + + if (loginAttemptService.isBlocked(username)) { + if (session != null) { + session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST"); + } + throw new LockedException( + "Your account has been locked due to too many failed login attempts."); + } + if (userService.usernameExistsIgnoreCase(username) + && userService.hasPassword(username) + && !userService.isAuthenticationTypeByUsername( + username, AuthenticationType.OAUTH2) + && saml2.getAutoCreateUser()) { + response.sendRedirect( + contextPath + "/logout?oauth2AuthenticationErrorWeb=true"); + return; + } + try { + if (saml2.getBlockRegistration() + && !userService.usernameExistsIgnoreCase(username)) { + response.sendRedirect( + contextPath + "/login?erroroauth=oauth2_admin_blocked_user"); + return; + } + userService.processOAuth2PostLogin(username, saml2.getAutoCreateUser()); + response.sendRedirect(contextPath + "/"); + return; + } catch (IllegalArgumentException e) { + response.sendRedirect(contextPath + "/logout?invalidUsername=true"); + return; + } + } + } else { + super.onAuthenticationSuccess(request, response, authentication); + } + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2ResponseAuthenticationConverter.java b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2ResponseAuthenticationConverter.java new file mode 100644 index 00000000000..b447eb8970b --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/saml2/CustomSaml2ResponseAuthenticationConverter.java @@ -0,0 +1,86 @@ +package stirling.software.SPDF.config.security.saml2; + +import java.util.*; + +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.schema.XSBoolean; +import org.opensaml.core.xml.schema.XSString; +import org.opensaml.saml.saml2.core.Assertion; +import org.opensaml.saml.saml2.core.Attribute; +import org.opensaml.saml.saml2.core.AttributeStatement; +import org.opensaml.saml.saml2.core.AuthnStatement; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken; +import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; +import stirling.software.SPDF.config.security.UserService; +import stirling.software.SPDF.model.User; + +@Component +@Slf4j +public class CustomSaml2ResponseAuthenticationConverter + implements Converter { + + private UserService userService; + + public CustomSaml2ResponseAuthenticationConverter(UserService userService) { + this.userService = userService; + } + + @Override + public Saml2Authentication convert(ResponseToken responseToken) { + // Extract the assertion from the response + Assertion assertion = responseToken.getResponse().getAssertions().get(0); + + // Extract the NameID + String nameId = assertion.getSubject().getNameID().getValue(); + + Optional userOpt = userService.findByUsernameIgnoreCase(nameId); + SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_USER"); + if (userOpt.isPresent()) { + User user = userOpt.get(); + if (user != null) { + simpleGrantedAuthority = + new SimpleGrantedAuthority(userService.findRole(user).getAuthority()); + } + } + + // Extract the SessionIndexes + List sessionIndexes = new ArrayList<>(); + for (AuthnStatement authnStatement : assertion.getAuthnStatements()) { + sessionIndexes.add(authnStatement.getSessionIndex()); + } + + // Extract the Attributes + Map> attributes = extractAttributes(assertion); + + // Create the custom principal + CustomSaml2AuthenticatedPrincipal principal = + new CustomSaml2AuthenticatedPrincipal(nameId, attributes, nameId, sessionIndexes); + + // Create the Saml2Authentication + return new Saml2Authentication( + principal, + responseToken.getToken().getSaml2Response(), + Collections.singletonList(simpleGrantedAuthority)); + } + + private Map> extractAttributes(Assertion assertion) { + Map> attributes = new HashMap<>(); + for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) { + for (Attribute attribute : attributeStatement.getAttributes()) { + String attributeName = attribute.getName(); + List values = new ArrayList<>(); + for (XMLObject xmlObject : attribute.getAttributeValues()) { + log.info("BOOL: " + ((XSBoolean) xmlObject).getValue()); + values.add(((XSString) xmlObject).getValue()); + } + attributes.put(attributeName, values); + } + } + return attributes; + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/session/SessionPersistentRegistry.java b/src/main/java/stirling/software/SPDF/config/security/session/SessionPersistentRegistry.java index f7f78d1e192..045b6375f87 100644 --- a/src/main/java/stirling/software/SPDF/config/security/session/SessionPersistentRegistry.java +++ b/src/main/java/stirling/software/SPDF/config/security/session/SessionPersistentRegistry.java @@ -16,6 +16,7 @@ import org.springframework.stereotype.Component; import jakarta.transaction.Transactional; +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal; import stirling.software.SPDF.model.SessionEntity; @Component @@ -50,6 +51,8 @@ public List getAllSessions( principalName = ((UserDetails) principal).getUsername(); } else if (principal instanceof OAuth2User) { principalName = ((OAuth2User) principal).getName(); + } else if (principal instanceof CustomSaml2AuthenticatedPrincipal) { + principalName = ((CustomSaml2AuthenticatedPrincipal) principal).getName(); } else if (principal instanceof String) { principalName = (String) principal; } @@ -79,6 +82,8 @@ public void registerNewSession(String sessionId, Object principal) { principalName = ((UserDetails) principal).getUsername(); } else if (principal instanceof OAuth2User) { principalName = ((OAuth2User) principal).getName(); + } else if (principal instanceof CustomSaml2AuthenticatedPrincipal) { + principalName = ((CustomSaml2AuthenticatedPrincipal) principal).getName(); } else if (principal instanceof String) { principalName = (String) principal; } diff --git a/src/main/java/stirling/software/SPDF/controller/api/UserController.java b/src/main/java/stirling/software/SPDF/controller/api/UserController.java index cc928749681..d27534875b1 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/UserController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/UserController.java @@ -32,6 +32,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import stirling.software.SPDF.config.security.UserService; +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.model.AuthenticationType; import stirling.software.SPDF.model.Role; @@ -336,6 +337,8 @@ public RedirectView changeUserEnabled( userNameP = ((UserDetails) principal).getUsername(); } else if (principal instanceof OAuth2User) { userNameP = ((OAuth2User) principal).getName(); + } else if (principal instanceof CustomSaml2AuthenticatedPrincipal) { + userNameP = ((CustomSaml2AuthenticatedPrincipal) principal).getName(); } else if (principal instanceof String) { userNameP = (String) principal; } diff --git a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java index 9081164d285..8dcb2a9a5fb 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java @@ -21,10 +21,13 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.model.*; +import stirling.software.SPDF.model.ApplicationProperties.Security; import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client; +import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2; import stirling.software.SPDF.model.provider.GithubProvider; import stirling.software.SPDF.model.provider.GoogleProvider; import stirling.software.SPDF.model.provider.KeycloakProvider; @@ -51,38 +54,54 @@ public String login(HttpServletRequest request, Model model, Authentication auth Map providerList = new HashMap<>(); - OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); + Security securityProps = applicationProperties.getSecurity(); + + OAUTH2 oauth = securityProps.getOauth2(); if (oauth != null) { - if (oauth.isSettingsValid()) { - providerList.put("oidc", oauth.getProvider()); - } - Client client = oauth.getClient(); - if (client != null) { - GoogleProvider google = client.getGoogle(); - if (google.isSettingsValid()) { - providerList.put(google.getName(), google.getClientName()); + if (oauth.getEnabled()) { + if (oauth.isSettingsValid()) { + providerList.put("/oauth2/authorization/oidc", oauth.getProvider()); } + Client client = oauth.getClient(); + if (client != null) { + GoogleProvider google = client.getGoogle(); + if (google.isSettingsValid()) { + providerList.put( + "/oauth2/authorization/" + google.getName(), + google.getClientName()); + } - GithubProvider github = client.getGithub(); - if (github.isSettingsValid()) { - providerList.put(github.getName(), github.getClientName()); - } + GithubProvider github = client.getGithub(); + if (github.isSettingsValid()) { + providerList.put( + "/oauth2/authorization/" + github.getName(), + github.getClientName()); + } - KeycloakProvider keycloak = client.getKeycloak(); - if (keycloak.isSettingsValid()) { - providerList.put(keycloak.getName(), keycloak.getClientName()); + KeycloakProvider keycloak = client.getKeycloak(); + if (keycloak.isSettingsValid()) { + providerList.put( + "/oauth2/authorization/" + keycloak.getName(), + keycloak.getClientName()); + } } } } + + SAML2 saml2 = securityProps.getSaml2(); + if (saml2 != null) { + if (saml2.getEnabled()) { + providerList.put("/saml2/authenticate/" + saml2.getRegistrationId(), "SAML 2"); + } + } // Remove any null keys/values from the providerList providerList .entrySet() .removeIf(entry -> entry.getKey() == null || entry.getValue() == null); model.addAttribute("providerlist", providerList); - model.addAttribute("loginMethod", applicationProperties.getSecurity().getLoginMethod()); - model.addAttribute( - "oAuth2Enabled", applicationProperties.getSecurity().getOauth2().getEnabled()); + model.addAttribute("loginMethod", securityProps.getLoginMethod()); + model.addAttribute("altLogin", securityProps.isAltLogin()); model.addAttribute("currentPage", "login"); @@ -349,6 +368,17 @@ public String account(HttpServletRequest request, Model model, Authentication au // Add oAuth2 Login attributes to the model model.addAttribute("oAuth2Login", true); } + if (principal instanceof CustomSaml2AuthenticatedPrincipal) { + // Cast the principal object to OAuth2User + CustomSaml2AuthenticatedPrincipal userDetails = + (CustomSaml2AuthenticatedPrincipal) principal; + + // Retrieve username and other attributes + username = userDetails.getName(); + // Add oAuth2 Login attributes to the model + model.addAttribute("oAuth2Login", true); + } + if (username != null) { // Fetch user details from the database Optional user = diff --git a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java index 10ad434aa0c..16397965ad7 100644 --- a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java +++ b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java @@ -1,13 +1,17 @@ package stirling.software.SPDF.model; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; @@ -18,6 +22,8 @@ import org.springframework.core.io.Resource; import lombok.Data; +import lombok.Getter; +import lombok.Setter; import lombok.ToString; import stirling.software.SPDF.config.YamlPropertySourceFactory; import stirling.software.SPDF.model.provider.GithubProvider; @@ -41,7 +47,6 @@ public class ApplicationProperties { private AutomaticallyGenerated automaticallyGenerated = new AutomaticallyGenerated(); private EnterpriseEdition enterpriseEdition = new EnterpriseEdition(); private AutoPipeline autoPipeline = new AutoPipeline(); - private static final Logger logger = LoggerFactory.getLogger(ApplicationProperties.class); @Data public static class AutoPipeline { @@ -63,41 +68,108 @@ public static class Security { private Boolean csrfDisabled; private InitialLogin initialLogin = new InitialLogin(); private OAUTH2 oauth2 = new OAUTH2(); - private SAML saml = new SAML(); + private SAML2 saml2 = new SAML2(); private int loginAttemptCount; private long loginResetTimeMinutes; private String loginMethod = "all"; + public Boolean isAltLogin() { + return saml2.getEnabled() || oauth2.getEnabled(); + } + + public enum LoginMethods { + ALL("all"), + NORMAL("normal"), + OAUTH2("oauth2"), + SAML2("saml2"); + + private String method; + + LoginMethods(String method) { + this.method = method; + } + + @Override + public String toString() { + return method; + } + } + + public boolean isUserPass() { + return (loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()) + || loginMethod.equalsIgnoreCase(LoginMethods.ALL.toString())); + } + + public boolean isOauth2Activ() { + return (oauth2 != null + && oauth2.getEnabled() + && !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString())); + } + + public boolean isSaml2Activ() { + return (saml2 != null + && saml2.getEnabled() + && !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString())); + } + @Data public static class InitialLogin { private String username; @ToString.Exclude private String password; } - @Data - public static class SAML { + @Getter + @Setter + public static class SAML2 { private Boolean enabled = false; - private String entityId; - private String registrationId; - private String spBaseUrl; - private String idpMetadataLocation; - private KeyStore keystore; + private Boolean autoCreateUser = false; + private Boolean blockRegistration = false; + private String registrationId = "stirling"; + private String idpMetadataUri; + private String idpSingleLogoutUrl; + private String idpSingleLoginUrl; + private String idpIssuer; + private String idpCert; + private String privateKey; + private String spCert; - @Data - public static class KeyStore { - private String keystoreLocation; - private String keystorePassword; - private String keyAlias; - private String keyPassword; - private String realmCertificateAlias; - - public Resource getKeystoreResource() { - if (keystoreLocation.startsWith("classpath:")) { - return new ClassPathResource( - keystoreLocation.substring("classpath:".length())); - } else { - return new FileSystemResource(keystoreLocation); - } + public InputStream getIdpMetadataUri() throws IOException { + if (idpMetadataUri.startsWith("classpath:")) { + return new ClassPathResource(idpMetadataUri.substring("classpath".length())) + .getInputStream(); + } + try { + URI uri = new URI(idpMetadataUri); + URL url = uri.toURL(); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + return connection.getInputStream(); + } catch (URISyntaxException e) { + throw new IOException("Invalid URI format: " + idpMetadataUri, e); + } + } + + public Resource getSpCert() { + if (spCert.startsWith("classpath:")) { + return new ClassPathResource(spCert.substring("classpath:".length())); + } else { + return new FileSystemResource(spCert); + } + } + + public Resource getidpCert() { + if (idpCert.startsWith("classpath:")) { + return new ClassPathResource(idpCert.substring("classpath:".length())); + } else { + return new FileSystemResource(idpCert); + } + } + + public Resource getPrivateKey() { + if (privateKey.startsWith("classpath:")) { + return new ClassPathResource(privateKey.substring("classpath:".length())); + } else { + return new FileSystemResource(privateKey); } } } diff --git a/src/main/java/stirling/software/SPDF/model/Provider.java b/src/main/java/stirling/software/SPDF/model/Provider.java index c378975d393..87f5fa29847 100644 --- a/src/main/java/stirling/software/SPDF/model/Provider.java +++ b/src/main/java/stirling/software/SPDF/model/Provider.java @@ -19,7 +19,6 @@ protected boolean isValid(String value, String name) { return true; } return false; - // throw new IllegalArgumentException(getName() + ": " + name + " is required!"); } protected boolean isValid(Collection value, String name) { @@ -27,66 +26,55 @@ protected boolean isValid(Collection value, String name) { return true; } return false; - // throw new IllegalArgumentException(getName() + ": " + name + " is required!"); } @Override public Collection getScopes() { - // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getScope'"); } @Override public void setScopes(String scopes) { - // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'setScope'"); } @Override public String getUseAsUsername() { - // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getUseAsUsername'"); } @Override public void setUseAsUsername(String useAsUsername) { - // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'setUseAsUsername'"); } @Override public String getIssuer() { - // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getIssuer'"); } @Override public void setIssuer(String issuer) { - // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'setIssuer'"); } @Override public String getClientSecret() { - // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getClientSecret'"); } @Override public void setClientSecret(String clientSecret) { - // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'setClientSecret'"); } @Override public String getClientId() { - // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'getClientId'"); } @Override public void setClientId(String clientId) { - // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'setClientId'"); } } diff --git a/src/main/resources/settings.yml.template b/src/main/resources/settings.yml.template index cd2481128e4..3ee1b9281cd 100644 --- a/src/main/resources/settings.yml.template +++ b/src/main/resources/settings.yml.template @@ -47,6 +47,18 @@ security: useAsUsername: email # Default is 'email'; custom fields can be used as the username scopes: openid, profile, email # Specify the scopes for which the application will request permissions provider: google # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak' + saml2: + enabled: false + autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users + blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin + registrationId: stirling + idpMetadataUri: https://dev-XXXXXXXX.okta.com/app/externalKey/sso/saml/metadata + idpSingleLogoutUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/slo/saml + idpSingleLoginUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/sso/saml + idpIssuer: http://www.okta.com/externalKey + idpCert: classpath:octa.crt + privateKey: classpath:saml-private-key.key + spCert: classpath:saml-public-cert.crt # Enterprise edition settings unused for now please ignore! enterpriseEdition: diff --git a/src/main/resources/templates/addUsers.html b/src/main/resources/templates/addUsers.html index 18bfb7d28ca..e17e165adbd 100644 --- a/src/main/resources/templates/addUsers.html +++ b/src/main/resources/templates/addUsers.html @@ -283,7 +283,7 @@