Merge branch 'main' into session_2025_03_22

This commit is contained in:
Ludy 2025-05-14 22:18:34 +02:00 committed by GitHub
commit d923e65b94
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 1846 additions and 252 deletions

View File

@ -24,4 +24,4 @@ jobs:
- name: "Checkout Repository" - name: "Checkout Repository"
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: "Dependency Review" - name: "Dependency Review"
uses: actions/dependency-review-action@ce3cf9537a52e8119d91fd484ab5b8a807627bf8 # v4.6.0 uses: actions/dependency-review-action@38ecb5b593bf0eb19e335c03f97670f792489a8b # v4.7.0

View File

@ -541,7 +541,7 @@ This would generate n entries of tr for each person in exampleData
```html ```html
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" th:href="@{/new-feature}">New Feature</a> <a class="nav-link" th:href="@{'/new-feature'}">New Feature</a>
</li> </li>
``` ```

View File

@ -112,7 +112,7 @@ Visit our comprehensive documentation at [docs.stirlingpdf.com](https://docs.sti
## Supported Languages ## Supported Languages
Stirling-PDF currently supports 39 languages! Stirling-PDF currently supports 40 languages!
| Language | Progress | | Language | Progress |
| -------------------------------------------- | -------------------------------------- | | -------------------------------------------- | -------------------------------------- |
@ -127,7 +127,7 @@ Stirling-PDF currently supports 39 languages!
| Dutch (Nederlands) (nl_NL) | ![79%](https://geps.dev/progress/79) | | Dutch (Nederlands) (nl_NL) | ![79%](https://geps.dev/progress/79) |
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) | | English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
| French (Français) (fr_FR) | ![91%](https://geps.dev/progress/91) | | French (Français) (fr_FR) | ![92%](https://geps.dev/progress/92) |
| German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) | | German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) |
| Greek (Ελληνικά) (el_GR) | ![91%](https://geps.dev/progress/91) | | Greek (Ελληνικά) (el_GR) | ![91%](https://geps.dev/progress/91) |
| Hindi (हिंदी) (hi_IN) | ![91%](https://geps.dev/progress/91) | | Hindi (हिंदी) (hi_IN) | ![91%](https://geps.dev/progress/91) |
@ -156,12 +156,12 @@ Stirling-PDF currently supports 39 languages!
| Turkish (Türkçe) (tr_TR) | ![97%](https://geps.dev/progress/97) | | Turkish (Türkçe) (tr_TR) | ![97%](https://geps.dev/progress/97) |
| Ukrainian (Українська) (uk_UA) | ![96%](https://geps.dev/progress/96) | | Ukrainian (Українська) (uk_UA) | ![96%](https://geps.dev/progress/96) |
| Vietnamese (Tiếng Việt) (vi_VN) | ![73%](https://geps.dev/progress/73) | | Vietnamese (Tiếng Việt) (vi_VN) | ![73%](https://geps.dev/progress/73) |
| Malayalam (മലയാളം) (ml_ML) | ![99%](https://geps.dev/progress/99) |
## Stirling PDF Enterprise ## Stirling PDF Enterprise
Stirling PDF offers an Enterprise edition of its software. This is the same great software but with added features, support and comforts. Stirling PDF offers an Enterprise edition of its software. This is the same great software but with added features, support and comforts.
Check out our [Enterprise docs](https://docs.stirlingpdf.com/Enterprise%20Edition) Check out our [Enterprise docs](https://docs.stirlingpdf.com/Pro)
## 🤝 Looking to contribute? ## 🤝 Looking to contribute?

View File

@ -29,7 +29,7 @@ ext {
} }
group = "stirling.software" group = "stirling.software"
version = "0.46.0" version = "0.46.1"
java { java {
// 17 is lowest but we support and recommend 21 // 17 is lowest but we support and recommend 21
@ -481,7 +481,7 @@ dependencies {
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion" testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
// Batik // Batik
implementation "org.apache.xmlgraphics:batik-all:1.18" implementation "org.apache.xmlgraphics:batik-all:1.19"
// TwelveMonkeys // TwelveMonkeys
runtimeOnly "com.twelvemonkeys.imageio:imageio-batik:$imageioVersion" runtimeOnly "com.twelvemonkeys.imageio:imageio-batik:$imageioVersion"
@ -529,7 +529,7 @@ dependencies {
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
implementation "io.micrometer:micrometer-core:1.14.6" implementation "io.micrometer:micrometer-core:1.14.7"
implementation group: "com.google.zxing", name: "core", version: "3.5.3" implementation group: "com.google.zxing", name: "core", version: "3.5.3"
// https://mvnrepository.com/artifact/org.commonmark/commonmark // https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation "org.commonmark:commonmark:0.24.0" implementation "org.commonmark:commonmark:0.24.0"

View File

@ -34,6 +34,11 @@ public class EEAppConfig {
return licenseKeyChecker.getPremiumLicenseEnabledResult() != License.NORMAL; return licenseKeyChecker.getPremiumLicenseEnabledResult() != License.NORMAL;
} }
@Bean(name = "license")
public String licenseType() {
return licenseKeyChecker.getPremiumLicenseEnabledResult().name();
}
@Bean(name = "runningEE") @Bean(name = "runningEE")
public boolean runningEnterprise() { public boolean runningEnterprise() {
return licenseKeyChecker.getPremiumLicenseEnabledResult() == License.ENTERPRISE; return licenseKeyChecker.getPremiumLicenseEnabledResult() == License.ENTERPRISE;

View File

@ -5,6 +5,7 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Properties; import java.util.Properties;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -15,6 +16,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
@ -33,6 +35,8 @@ public class AppConfig {
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
private final Environment env;
@Bean @Bean
@ConditionalOnProperty(name = "system.customHTMLFiles", havingValue = "true") @ConditionalOnProperty(name = "system.customHTMLFiles", havingValue = "true")
public SpringTemplateEngine templateEngine(ResourceLoader resourceLoader) { public SpringTemplateEngine templateEngine(ResourceLoader resourceLoader) {
@ -193,4 +197,37 @@ public class AppConfig {
public String uuid() { public String uuid() {
return applicationProperties.getAutomaticallyGenerated().getUUID(); return applicationProperties.getAutomaticallyGenerated().getUUID();
} }
@Bean(name = "disablePixel")
public boolean disablePixel() {
return Boolean.getBoolean(env.getProperty("DISABLE_PIXEL"));
}
@Bean(name = "machineType")
public String determineMachineType() {
try {
boolean isDocker = runningInDocker();
boolean isKubernetes = System.getenv("KUBERNETES_SERVICE_HOST") != null;
boolean isBrowserOpen = "true".equalsIgnoreCase(env.getProperty("BROWSER_OPEN"));
if (isKubernetes) {
return "Kubernetes";
} else if (isDocker) {
return "Docker";
} else if (isBrowserOpen) {
String os = System.getProperty("os.name").toLowerCase(Locale.ROOT);
if (os.contains("win")) {
return "Client-windows";
} else if (os.contains("mac")) {
return "Client-mac";
} else {
return "Client-unix";
}
} else {
return "Server-jar";
}
} catch (Exception e) {
return "Unknown";
}
}
} }

View File

@ -73,7 +73,7 @@ public class InitialSetup {
// Initialize Terms and Conditions // Initialize Terms and Conditions
String termsUrl = applicationProperties.getLegal().getTermsAndConditions(); String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
if (StringUtils.isEmpty(termsUrl)) { if (StringUtils.isEmpty(termsUrl)) {
String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions"; String defaultTermsUrl = "https://www.stirlingpdf.com/terms";
GeneralUtils.saveKeyToSettings("legal.termsAndConditions", defaultTermsUrl); GeneralUtils.saveKeyToSettings("legal.termsAndConditions", defaultTermsUrl);
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl); applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
} }

View File

@ -15,7 +15,7 @@ public class MetricsConfig {
return new MeterFilter() { return new MeterFilter() {
@Override @Override
public MeterFilterReply accept(Meter.Id id) { public MeterFilterReply accept(Meter.Id id) {
if (id.getName().equals("http.requests")) { if ("http.requests".equals(id.getName())) {
return MeterFilterReply.NEUTRAL; return MeterFilterReply.NEUTRAL;
} }
return MeterFilterReply.DENY; return MeterFilterReply.DENY;

View File

@ -5,7 +5,9 @@ import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.security.SecurityScheme;
@ -31,14 +33,25 @@ public class OpenApiConfig {
// default version if all else fails // default version if all else fails
version = "1.0.0"; version = "1.0.0";
} }
if (!applicationProperties.getSecurity().getEnableLogin()) { Info info =
return new OpenAPI()
.components(new Components())
.info(
new Info() new Info()
.title(DEFAULT_TITLE) .title(DEFAULT_TITLE)
.version(version) .version(version)
.description(DEFAULT_DESCRIPTION)); .license(
new License()
.name("MIT")
.url(
"https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/LICENSE")
.identifier("MIT"))
.termsOfService("https://www.stirlingpdf.com/terms")
.contact(
new Contact()
.name("Stirling Software")
.url("https://www.stirlingpdf.com")
.email("contact@stirlingpdf.com"))
.description(DEFAULT_DESCRIPTION);
if (!applicationProperties.getSecurity().getEnableLogin()) {
return new OpenAPI().components(new Components()).info(info);
} else { } else {
SecurityScheme apiKeyScheme = SecurityScheme apiKeyScheme =
new SecurityScheme() new SecurityScheme()
@ -47,11 +60,7 @@ public class OpenApiConfig {
.name("X-API-KEY"); .name("X-API-KEY");
return new OpenAPI() return new OpenAPI()
.components(new Components().addSecuritySchemes("apiKey", apiKeyScheme)) .components(new Components().addSecuritySchemes("apiKey", apiKeyScheme))
.info( .info(info)
new Info()
.title(DEFAULT_TITLE)
.version(version)
.description(DEFAULT_DESCRIPTION))
.addSecurityItem(new SecurityRequirement().addList("apiKey")); .addSecurityItem(new SecurityRequirement().addList("apiKey"));
} }
} }

View File

@ -180,14 +180,15 @@ public class AnalysisController {
// Get permissions // Get permissions
Map<String, Boolean> permissions = new HashMap<>(); Map<String, Boolean> permissions = new HashMap<>();
permissions.put("canPrint", document.getCurrentAccessPermission().canPrint()); permissions.put("preventPrinting", !document.getCurrentAccessPermission().canPrint());
permissions.put("canModify", document.getCurrentAccessPermission().canModify());
permissions.put( permissions.put(
"canExtractContent", "preventModify", !document.getCurrentAccessPermission().canModify());
document.getCurrentAccessPermission().canExtractContent());
permissions.put( permissions.put(
"canModifyAnnotations", "preventExtractContent",
document.getCurrentAccessPermission().canModifyAnnotations()); !document.getCurrentAccessPermission().canExtractContent());
permissions.put(
"preventModifyAnnotations",
!document.getCurrentAccessPermission().canModifyAnnotations());
securityInfo.put("permissions", permissions); securityInfo.put("permissions", permissions);
} else { } else {

View File

@ -627,23 +627,30 @@ public class CompressController {
// Scale factors for different optimization levels // Scale factors for different optimization levels
private double getScaleFactorForLevel(int optimizeLevel) { private double getScaleFactorForLevel(int optimizeLevel) {
return switch (optimizeLevel) { return switch (optimizeLevel) {
case 4 -> 0.9; // 90% - lite compression case 3 -> 0.85;
case 5 -> 0.8; // 80% - lite compression case 4 -> 0.75;
case 6 -> 0.7; // 70% - lite compression case 5 -> 0.65;
case 7 -> 0.6; // 60% - intense compression case 6 -> 0.55;
case 8 -> 0.5; // 50% - intense compression case 7 -> 0.45;
case 9, 10 -> 0.4; // 40% - intense compression case 8 -> 0.35;
default -> 1.0; // No scaling for levels 1-3 case 9 -> 0.25;
case 10 -> 0.15;
default -> 1.0;
}; };
} }
// JPEG quality for different optimization levels // JPEG quality for different optimization levels
private float getJpegQualityForLevel(int optimizeLevel) { private float getJpegQualityForLevel(int optimizeLevel) {
return switch (optimizeLevel) { return switch (optimizeLevel) {
case 7 -> 0.8f; // 80% quality case 3 -> 0.85f;
case 8 -> 0.6f; // 60% quality case 4 -> 0.80f;
case 9, 10 -> 0.4f; // 40% quality case 5 -> 0.75f;
default -> 0.7f; // 70% quality for levels 1-6 case 6 -> 0.70f;
case 7 -> 0.60f;
case 8 -> 0.50f;
case 9 -> 0.35f;
case 10 -> 0.2f;
default -> 0.7f;
}; };
} }
@ -698,7 +705,7 @@ public class CompressController {
while (!sizeMet && optimizeLevel <= 9) { while (!sizeMet && optimizeLevel <= 9) {
// Apply image compression for levels 4-9 // Apply image compression for levels 4-9
if ((optimizeLevel >= 4 || Boolean.TRUE.equals(convertToGrayscale)) if ((optimizeLevel >= 3 || Boolean.TRUE.equals(convertToGrayscale))
&& !imageCompressionApplied) { && !imageCompressionApplied) {
double scaleFactor = getScaleFactorForLevel(optimizeLevel); double scaleFactor = getScaleFactorForLevel(optimizeLevel);
float jpegQuality = getJpegQualityForLevel(optimizeLevel); float jpegQuality = getJpegQualityForLevel(optimizeLevel);
@ -790,10 +797,14 @@ public class CompressController {
log.info("Pre-QPDF file size: {}", GeneralUtils.formatBytes(preQpdfSize)); log.info("Pre-QPDF file size: {}", GeneralUtils.formatBytes(preQpdfSize));
// Map optimization levels to QPDF compression levels // Map optimization levels to QPDF compression levels
int qpdfCompressionLevel = int qpdfCompressionLevel;
optimizeLevel <= 3 if (optimizeLevel == 1) {
? optimizeLevel * 3 // Level 1->3, 2->6, 3->9 qpdfCompressionLevel = 5;
: 9; // Max compression for levels 4-9 } else if (optimizeLevel == 2) {
qpdfCompressionLevel = 9;
} else {
qpdfCompressionLevel = 9;
}
// Create output file for QPDF // Create output file for QPDF
Path qpdfOutputFile = Files.createTempFile("qpdf_output_", ".pdf"); Path qpdfOutputFile = Files.createTempFile("qpdf_output_", ".pdf");

View File

@ -63,25 +63,25 @@ public class PasswordController {
String ownerPassword = request.getOwnerPassword(); String ownerPassword = request.getOwnerPassword();
String password = request.getPassword(); String password = request.getPassword();
int keyLength = request.getKeyLength(); int keyLength = request.getKeyLength();
boolean canAssembleDocument = request.isCanAssembleDocument(); boolean preventAssembly = request.isPreventAssembly();
boolean canExtractContent = request.isCanExtractContent(); boolean preventExtractContent = request.isPreventExtractContent();
boolean canExtractForAccessibility = request.isCanExtractForAccessibility(); boolean preventExtractForAccessibility = request.isPreventExtractForAccessibility();
boolean canFillInForm = request.isCanFillInForm(); boolean preventFillInForm = request.isPreventFillInForm();
boolean canModify = request.isCanModify(); boolean preventModify = request.isPreventModify();
boolean canModifyAnnotations = request.isCanModifyAnnotations(); boolean preventModifyAnnotations = request.isPreventModifyAnnotations();
boolean canPrint = request.isCanPrint(); boolean preventPrinting = request.isPreventPrinting();
boolean canPrintFaithful = request.isCanPrintFaithful(); boolean preventPrintingFaithful = request.isPreventPrintingFaithful();
PDDocument document = pdfDocumentFactory.load(fileInput); PDDocument document = pdfDocumentFactory.load(fileInput);
AccessPermission ap = new AccessPermission(); AccessPermission ap = new AccessPermission();
ap.setCanAssembleDocument(!canAssembleDocument); ap.setCanAssembleDocument(!preventAssembly);
ap.setCanExtractContent(!canExtractContent); ap.setCanExtractContent(!preventExtractContent);
ap.setCanExtractForAccessibility(!canExtractForAccessibility); ap.setCanExtractForAccessibility(!preventExtractForAccessibility);
ap.setCanFillInForm(!canFillInForm); ap.setCanFillInForm(!preventFillInForm);
ap.setCanModify(!canModify); ap.setCanModify(!preventModify);
ap.setCanModifyAnnotations(!canModifyAnnotations); ap.setCanModifyAnnotations(!preventModifyAnnotations);
ap.setCanPrint(!canPrint); ap.setCanPrint(!preventPrinting);
ap.setCanPrintFaithful(!canPrintFaithful); ap.setCanPrintFaithful(!preventPrintingFaithful);
StandardProtectionPolicy spp = new StandardProtectionPolicy(ownerPassword, password, ap); StandardProtectionPolicy spp = new StandardProtectionPolicy(ownerPassword, password, ap);
if (!"".equals(ownerPassword) || !"".equals(password)) { if (!"".equals(ownerPassword) || !"".equals(password)) {

View File

@ -170,15 +170,16 @@ public class RedactController {
} }
private Color decodeOrDefault(String hex, Color defaultColor) { private Color decodeOrDefault(String hex, Color defaultColor) {
Color color = null;
try { try {
color = Color.decode(hex); if (hex != null && !hex.startsWith("#")) {
hex = "#" + hex;
}
return Color.decode(hex);
} catch (Exception e) { } catch (Exception e) {
color = defaultColor; return defaultColor;
}
} }
return color;
}
private List<Integer> getPageNumbers(ManualRedactPdfRequest request, int pagesCount) { private List<Integer> getPageNumbers(ManualRedactPdfRequest request, int pagesCount) {
String pageNumbersInput = request.getPageNumbers(); String pageNumbersInput = request.getPageNumbers();

View File

@ -29,31 +29,29 @@ public class AddPasswordRequest extends PDFFile {
defaultValue = "256") defaultValue = "256")
private int keyLength = 256; private int keyLength = 256;
@Schema(description = "Whether the document assembly is allowed", example = "false") @Schema(description = "Whether document assembly is prevented", example = "false")
private boolean canAssembleDocument; private boolean preventAssembly;
@Schema(description = "Whether content extraction is prevented", example = "false")
private boolean preventExtractContent;
@Schema( @Schema(
description = "Whether content extraction for accessibility is allowed", description = "Whether content extraction for accessibility is prevented",
example = "false") example = "false")
private boolean canExtractContent; private boolean preventExtractForAccessibility;
@Schema( @Schema(description = "Whether form filling is prevented", example = "false")
description = "Whether content extraction for accessibility is allowed", private boolean preventFillInForm;
example = "false")
private boolean canExtractForAccessibility;
@Schema(description = "Whether form filling is allowed", example = "false") @Schema(description = "Whether document modification is prevented", example = "false")
private boolean canFillInForm; private boolean preventModify;
@Schema(description = "Whether the document modification is allowed", example = "false") @Schema(description = "Whether modification of annotations is prevented", example = "false")
private boolean canModify; private boolean preventModifyAnnotations;
@Schema(description = "Whether modification of annotations is allowed", example = "false") @Schema(description = "Whether printing of the document is prevented", example = "false")
private boolean canModifyAnnotations; private boolean preventPrinting;
@Schema(description = "Whether printing of the document is allowed", example = "false") @Schema(description = "Whether faithful printing is prevented", example = "false")
private boolean canPrint; private boolean preventPrintingFaithful;
@Schema(description = "Whether faithful printing is allowed", example = "false")
private boolean canPrintFaithful;
} }

View File

@ -23,7 +23,7 @@ public class RedactPdfRequest extends PDFFile {
@Schema(description = "Whether to use whole word search", defaultValue = "false") @Schema(description = "Whether to use whole word search", defaultValue = "false")
private boolean wholeWordSearch; private boolean wholeWordSearch;
@Schema(description = "The color for redaction", defaultValue = "#000000") @Schema(description = "Hexadecimal color code for redaction, e.g. #FF0000 or 000000", defaultValue = "#000000")
private String redactColor = "#000000"; private String redactColor = "#000000";
@Schema(description = "Custom padding for redaction", type = "number") @Schema(description = "Custom padding for redaction", type = "number")

View File

@ -517,13 +517,13 @@ home.showJS.title=Afficher le JavaScript
home.showJS.desc=Recherche et affiche tout JavaScript injecté dans un PDF. home.showJS.desc=Recherche et affiche tout JavaScript injecté dans un PDF.
showJS.tags=JS showJS.tags=JS
home.autoRedact.title=Censure automatique home.autoRedact.title=Caviardage automatique
home.autoRedact.desc=Censurer automatiquement les informations sensibles d'un PDF. home.autoRedact.desc=Caviardez automatiquement les informations sensibles d'un PDF.
autoRedact.tags=caviarder,rédiger,censurer,redact,auto autoRedact.tags=caviarder,redact,auto,Masquer,noircir,noir,marqueur,caché,rédiger,censurer
home.redact.title=Censure manuelle home.redact.title=Caviardage manuel
home.redact.desc=Censurer un PDF en fonction de texte sélectionné, formes dessinées et/ou des pages sélectionnées. home.redact.desc=Caviarder un PDF en fonction de texte sélectionné, formes dessinées et/ou des pages sélectionnées.
redact.tags=Redact,Hide,black out,black,marker,hidden,manual,caviarder,rédiger,censurer redact.tags=Caviarder,Redact,Masquer,noircir,noir,marqueur,caché,rédiger,censurer
home.tableExtraxt.title=PDF en CSV home.tableExtraxt.title=PDF en CSV
home.tableExtraxt.desc=Extrait les tableaux d'un PDF et les transforme en CSV. home.tableExtraxt.desc=Extrait les tableaux d'un PDF et les transforme en CSV.
@ -624,31 +624,31 @@ autoRedact.convertPDFToImageLabel=Convertir un PDF en PDF-Image (utilisé pour s
autoRedact.submitButton=Caviarder autoRedact.submitButton=Caviarder
#redact #redact
redact.title=Rédaction manuelle redact.title=Caviardage manuel
redact.header=Rédaction manuelle redact.header=Caviardage manuel
redact.submit=Rédiger redact.submit=Caviarder
redact.textBasedRedaction=Rédaction en fonction de texte redact.textBasedRedaction=Caviarder du texte
redact.pageBasedRedaction=Rédaction en fonction de pages redact.pageBasedRedaction=Caviarder des pages
redact.convertPDFToImageLabel=Convertir en PDF-Image (pour supprimer le texte derrière le rectangle) redact.convertPDFToImageLabel=Convertir en PDF-Image (pour supprimer le texte derrière le rectangle)
redact.pageRedactionNumbers.title=Pages redact.pageRedactionNumbers.title=Pages
redact.pageRedactionNumbers.placeholder=(ex: 1,2,8 ou 4,7,12-16 ou 2n-1) redact.pageRedactionNumbers.placeholder=(ex: 1,2,8 ou 4,7,12-16 ou 2n-1)
redact.redactionColor.title=Couleur redact.redactionColor.title=Couleur
redact.export=Exporter redact.export=Exporter
redact.upload=Téléverser redact.upload=Téléverser
redact.boxRedaction=Dessiner le rectangle à rédiger redact.boxRedaction=Tracer le rectangle à caviarder
redact.zoom=Zoom redact.zoom=Zoom
redact.zoomIn=Zoom avant redact.zoomIn=Zoom avant
redact.zoomOut=Zoom arrière redact.zoomOut=Zoom arrière
redact.nextPage=Page suivante redact.nextPage=Page suivante
redact.previousPage=Page précédente redact.previousPage=Page précédente
redact.toggleSidebar=Toggle Sidebar redact.toggleSidebar=Montrer la barre latérale
redact.showThumbnails=Afficher les miniatures redact.showThumbnails=Afficher les miniatures
redact.showDocumentOutline=Montrer les contours du document (double-click pour agrandir/réduire tous les éléments) redact.showDocumentOutline=Montrer les contours du document (double-click pour agrandir/réduire tous les éléments)
redact.showAttatchments=Montrer les éléments attachés redact.showAttatchments=Montrer les éléments attachés
redact.showLayers=Montrer les calques (double-click pour réinitialiser tous les calques à l'état par défaut) redact.showLayers=Montrer les calques (double-click pour réinitialiser tous les calques à l'état par défaut)
redact.colourPicker=Sélection de couleur redact.colourPicker=Sélection de couleur
redact.findCurrentOutlineItem=Trouver l'élément de contour courrant redact.findCurrentOutlineItem=Trouver l'élément de contour courrant
redact.applyChanges=Apply Changes redact.applyChanges=Appliquer les changements
#showJS #showJS
showJS.title=Afficher le JavaScript showJS.title=Afficher le JavaScript

File diff suppressed because it is too large Load Diff

View File

@ -77,7 +77,7 @@ premium:
appId: '' appId: ''
mail: mail:
enabled: true # set to 'true' to enable sending emails enabled: false # set to 'true' to enable sending emails
host: smtp.example.com # SMTP server hostname host: smtp.example.com # SMTP server hostname
port: 587 # SMTP server port port: 587 # SMTP server port
username: '' # SMTP server username username: '' # SMTP server username
@ -85,7 +85,7 @@ mail:
from: '' # sender email address from: '' # sender email address
legal: legal:
termsAndConditions: https://www.stirlingpdf.com/terms-and-conditions # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder termsAndConditions: https://www.stirlingpdf.com/terms # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder
privacyPolicy: https://www.stirlingpdf.com/privacy-policy # URL to the privacy policy of your application (e.g. https://example.com/privacy). Empty string to disable or filename to load from local file in static folder privacyPolicy: https://www.stirlingpdf.com/privacy-policy # URL to the privacy policy of your application (e.g. https://example.com/privacy). Empty string to disable or filename to load from local file in static folder
accessibilityStatement: '' # URL to the accessibility statement of your application (e.g. https://example.com/accessibility). Empty string to disable or filename to load from local file in static folder accessibilityStatement: '' # URL to the accessibility statement of your application (e.g. https://example.com/accessibility). Empty string to disable or filename to load from local file in static folder
cookiePolicy: '' # URL to the cookie policy of your application (e.g. https://example.com/cookie). Empty string to disable or filename to load from local file in static folder cookiePolicy: '' # URL to the cookie policy of your application (e.g. https://example.com/cookie). Empty string to disable or filename to load from local file in static folder
@ -113,11 +113,11 @@ system:
name: postgres # set the name of your database. Should match the name of the database you create name: postgres # set the name of your database. Should match the name of the database you create
customPaths: customPaths:
pipeline: pipeline:
watchedFoldersDir: '' #Defaults to /pipeline/watchedFolders watchedFoldersDir: '' # Defaults to /pipeline/watchedFolders
finishedFoldersDir: '' #Defaults to /pipeline/finishedFolders finishedFoldersDir: '' # Defaults to /pipeline/finishedFolders
operations: operations:
weasyprint: '' #Defaults to /opt/venv/bin/weasyprint weasyprint: '' # Defaults to /opt/venv/bin/weasyprint
unoconvert: '' #Defaults to /opt/venv/bin/unoconvert unoconvert: '' # Defaults to /opt/venv/bin/unoconvert
fileUploadLimit: '' # Defaults to "". No limit when string is empty. Set a number, between 0 and 999, followed by one of the following strings to set a limit. "KB", "MB", "GB". fileUploadLimit: '' # Defaults to "". No limit when string is empty. Set a number, between 0 and 999, followed by one of the following strings to set a limit. "KB", "MB", "GB".
ui: ui:

View File

@ -1,7 +1,8 @@
#page-container { #page-container {
min-height: 100vh; height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow-x: clip;
} }
#content-wrap { #content-wrap {

View File

@ -162,6 +162,7 @@ html[dir="rtl"] .lang-dropdown-item-wrapper {
text-wrap: wrap; text-wrap: wrap;
word-break: break-word; word-break: break-word;
width: 80%; width: 80%;
font-size: large;
} }
.close-icon { .close-icon {
@ -476,6 +477,9 @@ html[dir="rtl"] .dropdown-menu[data-bs-popper] {
display: flex; display: flex;
gap: 30px; gap: 30px;
justify-content: center; justify-content: center;
width: 140%;
position: relative;
left: -20%;
} }
.feature-group { .feature-group {

View File

@ -4,10 +4,21 @@ document.addEventListener('DOMContentLoaded', function () {
if (window.analyticsPromptBoolean) { if (window.analyticsPromptBoolean) {
const analyticsModal = new bootstrap.Modal(document.getElementById('analyticsModal')); const analyticsModal = new bootstrap.Modal(document.getElementById('analyticsModal'));
analyticsModal.show(); analyticsModal.show();
let retryCount = 0;
function hideCookieBanner() {
const cookieBanner = document.querySelector('#cc-main');
if (cookieBanner && cookieBanner.offsetHeight > 0) {
cookieBanner.style.display = "none";
} else if (retryCount < 20) {
retryCount++;
setTimeout(hideCookieBanner, 100);
}
}
hideCookieBanner();
} }
}); });
/*]]>*/ /*]]>*/function setAnalytics(enabled) {
function setAnalytics(enabled) {
fetchWithCsrf('api/v1/settings/update-enable-analytics', { fetchWithCsrf('api/v1/settings/update-enable-analytics', {
method: 'POST', method: 'POST',
headers: { headers: {
@ -19,6 +30,15 @@ function setAnalytics(enabled) {
if (response.status === 200) { if (response.status === 200) {
console.log('Analytics setting updated successfully'); console.log('Analytics setting updated successfully');
bootstrap.Modal.getInstance(document.getElementById('analyticsModal')).hide(); bootstrap.Modal.getInstance(document.getElementById('analyticsModal')).hide();
if (typeof CookieConsent !== "undefined") {
if (enabled) {
CookieConsent.acceptCategory(['analytics']);
} else {
CookieConsent.acceptCategory([]);
}
}
} else if (response.status === 208) { } else if (response.status === 208) {
console.log('Analytics setting has already been set. Please edit /config/settings.yml to change it.', response); console.log('Analytics setting has already been set. Please edit /config/settings.yml to change it.', response);
alert('Analytics setting has already been set. Please edit /config/settings.yml to change it.'); alert('Analytics setting has already been set. Please edit /config/settings.yml to change it.');

View File

@ -299,21 +299,42 @@ document.getElementById("addOperationBtn").addEventListener("click", function ()
} }
} }
listItem.innerHTML = ` let containerDiv = document.createElement("div");
<div class="d-flex justify-content-between align-items-center w-100"> containerDiv.className = "d-flex justify-content-between align-items-center w-100";
<div class="operationName">${selectedOperation}</div>
<div class="arrows d-flex"> let operationNameDiv = document.createElement("div");
<button class="btn btn-secondary move-up ms-1"><span class="material-symbols-rounded">arrow_upward</span></button> operationNameDiv.className = "operationName";
<button class="btn btn-secondary move-down ms-1"><span class="material-symbols-rounded">arrow_downward</span></button> operationNameDiv.textContent = selectedOperation;
<button class="btn ${hasSettings ? "btn-warning" : "btn-secondary"} pipelineSettings ms-1" ${ containerDiv.appendChild(operationNameDiv);
hasSettings ? "" : "disabled"
}> let arrowsDiv = document.createElement("div");
<span class="material-symbols-rounded">settings</span> arrowsDiv.className = "arrows d-flex";
</button>
<button class="btn btn-danger remove ms-1"><span class="material-symbols-rounded">close</span></button> let moveUpButton = document.createElement("button");
</div> moveUpButton.className = "btn btn-secondary move-up ms-1";
</div> moveUpButton.innerHTML = '<span class="material-symbols-rounded">arrow_upward</span>';
`; arrowsDiv.appendChild(moveUpButton);
let moveDownButton = document.createElement("button");
moveDownButton.className = "btn btn-secondary move-down ms-1";
moveDownButton.innerHTML = '<span class="material-symbols-rounded">arrow_downward</span>';
arrowsDiv.appendChild(moveDownButton);
let settingsButton = document.createElement("button");
settingsButton.className = `btn ${hasSettings ? "btn-warning" : "btn-secondary"} pipelineSettings ms-1`;
if (!hasSettings) {
settingsButton.disabled = true;
}
settingsButton.innerHTML = '<span class="material-symbols-rounded">settings</span>';
arrowsDiv.appendChild(settingsButton);
let removeButton = document.createElement("button");
removeButton.className = "btn btn-danger remove ms-1";
removeButton.innerHTML = '<span class="material-symbols-rounded">close</span>';
arrowsDiv.appendChild(removeButton);
containerDiv.appendChild(arrowsDiv);
listItem.appendChild(containerDiv);
pipelineList.appendChild(listItem); pipelineList.appendChild(listItem);

View File

@ -9,7 +9,7 @@ TabContainer = {
tabList.classList.add('tab-buttons'); tabList.classList.add('tab-buttons');
tabTitles.forEach((title) => { tabTitles.forEach((title) => {
const tabButton = document.createElement('button'); const tabButton = document.createElement('button');
tabButton.innerHTML = title; tabButton.textContent = title;
tabButton.onclick = (e) => { tabButton.onclick = (e) => {
this.setActiveTab(e.target); this.setActiveTab(e.target);
}; };

View File

@ -35,16 +35,32 @@
</div> </div>
<!-- User Settings Title --> <!-- User Settings Title -->
<div <div style="background: var(--md-sys-color-outline-variant);padding: .8rem; margin: 10px 0; border-radius: 2rem; text-align: center;">
style="background: var(--md-sys-color-outline-variant);padding: .8rem; margin: 10px 0; border-radius: 2rem; text-align: center;"> <a href="#"
<a href="#" th:data-bs-toggle="${totalUsers >= maxPaidUsers} ? null : 'modal'" th:data-bs-toggle="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? null : 'modal'"
th:data-bs-target="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? null : '#addUserModal'" th:data-bs-target="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? null : '#addUserModal'"
th:class="${totalUsers >= maxPaidUsers} ? 'btn btn-danger' : 'btn btn-outline-success'" th:class="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? 'btn btn-danger' : 'btn btn-outline-success'"
th:title="${totalUsers >= maxPaidUsers} ? #{adminUserSettings.maxUsersReached} : #{adminUserSettings.addUser}"> th:title="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? #{adminUserSettings.maxUsersReached} : #{adminUserSettings.addUser}">
<span class="material-symbols-rounded">person_add</span> <span class="material-symbols-rounded">person_add</span>
<span th:text="#{adminUserSettings.addUser}">Add New User</span> <span th:text="#{adminUserSettings.addUser}">Add New User</span>
</a> </a>
<a href="#"
data-bs-toggle="modal"
data-bs-target="#changeUserRoleModal"
class="btn btn-outline-success"
th:title="#{adminUserSettings.changeUserRole}">
<span class="material-symbols-rounded">edit</span>
<span th:text="#{adminUserSettings.changeUserRole}">Change User's Role</span>
</a>
<a th:href="@{'/usage'}" th:if="${@runningEE}"
class="btn btn-outline-success"
th:title="#{adminUserSettings.usage}">
<span class="material-symbols-rounded">analytics</span>
<span th:text="#{adminUserSettings.usage}">Usage Statistics</span>
</a>
<a href="#" data-bs-toggle="modal" data-bs-target="#changeUserRoleModal" class="btn btn-outline-success" <a href="#" data-bs-toggle="modal" data-bs-target="#changeUserRoleModal" class="btn btn-outline-success"
th:title="#{adminUserSettings.changeUserRole}"> th:title="#{adminUserSettings.changeUserRole}">
<span class="material-symbols-rounded">edit</span> <span class="material-symbols-rounded">edit</span>
@ -400,10 +416,8 @@
checkboxContainer.slideDown('fast'); checkboxContainer.slideDown('fast');
} }
}); });
});
</script> </script>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</body> </body>
</html> </html>

View File

@ -41,7 +41,13 @@
<link rel="stylesheet" th:href="@{'/css/bootstrap-icons.min.css'}"> <link rel="stylesheet" th:href="@{'/css/bootstrap-icons.min.css'}">
<!-- Pixel, doesn't collect any PII--> <!-- Pixel, doesn't collect any PII-->
<img referrerpolicy="no-referrer-when-downgrade" src="https://pixel.stirlingpdf.com/a.png?x-pxid=4f5fa02f-a065-4efb-bb2c-24509a4b6b92" style="position: absolute; visibility: hidden;"/> <img th:if="${!@disablePixel}" referrerpolicy="no-referrer-when-downgrade"
th:src="'https://pixel.stirlingpdf.com/a.png?x-pxid=4f5fa02f-a065-4efb-bb2c-24509a4b6b92'
+ '&machineType=' + ${@machineType}
+ '&appVersion=' + ${@appVersion}
+ '&licenseType=' + ${@license}
+ '&loginEnabled=' + ${@loginEnabled}"
style="position: absolute; visibility: hidden;" />
<!-- Custom --> <!-- Custom -->
<link rel="stylesheet" th:href="@{'/css/general.css'}"> <link rel="stylesheet" th:href="@{'/css/general.css'}">

View File

@ -1,43 +1,44 @@
<th:block th:fragment="langs"> <th:block th:fragment="langs">
<div th:replace="~{fragments/languageEntry :: languageEntry ('bg_BG', 'Български')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('bg_BG', 'Български')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('ar_AR', 'العربية')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('ar_AR', 'العربية')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('ca_CA', 'Català')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('ca_CA', 'Català')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('zh_CN', '简体中文')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('zh_CN', '简体中文')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('zh_TW', '繁體中文')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('zh_TW', '繁體中文')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('zh_BO', 'བོད་ཡིག')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('zh_BO', 'བོད་ཡིག')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('az_AZ', 'Azərbaycan Dili')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('az_AZ', 'Azərbaycan Dili')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('da_DK', 'Dansk')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('da_DK', 'Dansk')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('de_DE', 'Deutsch')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('de_DE', 'Deutsch')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('en_GB', 'English (GB)')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('en_GB', 'English (GB)')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('en_US', 'English (US)')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('en_US', 'English (US)')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('eu_ES', 'Euskara')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('eu_ES', 'Euskara')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('es_ES', 'Español')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('es_ES', 'Español')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('fr_FR', 'Français')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('fr_FR', 'Français')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('id_ID', 'Bahasa Indonesia')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('id_ID', 'Bahasa Indonesia')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('ga_IE', 'Gaeilge')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('ga_IE', 'Gaeilge')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('it_IT', 'Italiano')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('it_IT', 'Italiano')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('nl_NL', 'Nederlands')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('nl_NL', 'Nederlands')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('fa_IR', 'پارسی')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('fa_IR', 'پارسی')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('pl_PL', 'Polski')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('pl_PL', 'Polski')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('pt_BR', 'Português (BR)')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('pt_BR', 'Português (BR)')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('pt_PT', 'Português (PT)')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('pt_PT', 'Português (PT)')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('ro_RO', 'Română')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('ro_RO', 'Română')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('sk_SK', 'Slovenčina')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('sk_SK', 'Slovenčina')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('sl_SI', 'Slovenščina')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('sl_SI', 'Slovenščina')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('sv_SE', 'Svenska')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('sv_SE', 'Svenska')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('tr_TR', 'Türkçe')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('tr_TR', 'Türkçe')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('ru_RU', 'Русский')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('ru_RU', 'Русский')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('ko_KR', '한국어')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('ko_KR', '한국어')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('ja_JP', '日本語')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('ja_JP', '日本語')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('el_GR', 'Ελληνικά')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('el_GR', 'Ελληνικά')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('hu_HU', 'Magyar')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('hu_HU', 'Magyar')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('hi_IN', 'हिन्दी')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('hi_IN', 'हिन्दी')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('sr_LATN_RS', 'Srpski')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('sr_LATN_RS', 'Srpski')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('uk_UA', 'Українська')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('uk_UA', 'Українська')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('cs_CZ', 'Česky')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('cs_CZ', 'Česky')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('hr_HR', 'Hrvatski')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('hr_HR', 'Hrvatski')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('no_NB', 'Norsk')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('no_NB', 'Norsk')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('th_TH', 'ไทย')}" ></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('th_TH', 'ไทย')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('vi_VN', 'Tiếng Việt')}"></div> <div th:replace="~{fragments/languageEntry :: languageEntry ('vi_VN', 'Tiếng Việt')}"></div>
<div th:replace="~{fragments/languageEntry :: languageEntry ('ml_IN', 'മലയാളം')}"></div>
</th:block> </th:block>

View File

@ -1,4 +1,4 @@
<div th:fragment="navbar" class="mx-auto" style="position: sticky; top:0; z-index:10000"> <div th:fragment="navbar" class="mx-auto" style="position: sticky; top:0; z-index:10000; width:100%">
<script th:src="@{'/js/languageSelection.js'}"></script> <script th:src="@{'/js/languageSelection.js'}"></script>
<script th:src="@{'/js/navbar.js'}"></script> <script th:src="@{'/js/navbar.js'}"></script>
<script th:src="@{'/js/additionalLanguageCode.js'}"></script> <script th:src="@{'/js/additionalLanguageCode.js'}"></script>

View File

@ -8,19 +8,17 @@
<body> <body>
<div id="page-container"> <div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block> <th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<div style="transform-origin: top;"
id="scale-wrap">
<br class="d-md-none"> <br class="d-md-none">
<!-- Features --> <!-- Features -->
<script th:src="@{'/js/homecard.js'}"></script> <script th:src="@{'/js/homecard.js'}"></script>
<div style=" <div style="
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column;"> flex-direction: column;"
>
<div> <div>
<br> <br>
<div style="justify-content: center; display: flex;"> <div style="justify-content: center; display: flex;">
@ -45,7 +43,7 @@
th:insert="~{fragments/navbarEntry :: navbarEntry ('multi-tool', 'construction', 'home.multiTool.title', 'home.multiTool.desc', 'multiTool.tags', 'organize')}"> th:insert="~{fragments/navbarEntry :: navbarEntry ('multi-tool', 'construction', 'home.multiTool.title', 'home.multiTool.desc', 'multiTool.tags', 'organize')}">
</div> </div>
<div class="newfeature" <div class="newfeature"
th:insert="~{fragments/navbarEntry :: navbarEntry('validate-signature', 'verified', 'home.validateSignature.title', 'home.validateSignature.desc', 'validateSignature.tags', 'security')}"> th:insert="~{fragments/navbarEntry :: navbarEntry('compress-pdf', 'zoom_in_map', 'home.compressPdfs.title', 'home.compressPdfs.desc', 'compressPDFs.tags', 'advance')}">
</div> </div>
</div> </div>
</div> </div>
@ -123,9 +121,10 @@
</div> </div>
</div> </div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</div>
</div> </div>
@ -222,6 +221,20 @@
window.showSurvey = /*[[${showSurveyFromDocker}]]*/ true window.showSurvey = /*[[${showSurveyFromDocker}]]*/ true
</script> </script>
<script th:src="@{'/js/pages/home.js'}" th:inline="javascript"></script> <script th:src="@{'/js/pages/home.js'}" th:inline="javascript"></script>
<script>
function applyScale() {
const baseWidth = 1440;
const baseHeight = 1000;
const scaleX = window.innerWidth / baseWidth;
const scaleY = window.innerHeight / baseHeight;
const scale = Math.max(0.9, Math.min(scaleX, scaleY)); // keep aspect ratio, honor minScale
const ui = document.getElementById('scale-wrap');
ui.style.transform = `scale(${scale*0.75})`;
}
window.addEventListener('resize', applyScale);
window.addEventListener('load', applyScale);
</script>
</body> </body>

View File

@ -41,36 +41,36 @@
<div class="mb-3"> <div class="mb-3">
<label class="mb-2" th:text="#{addPassword.selectText.5}"></label> <label class="mb-2" th:text="#{addPassword.selectText.5}"></label>
<div class="form-check ms-3"> <div class="form-check ms-3">
<input type="checkbox" id="canAssembleDocument" name="canAssembleDocument"> <input type="checkbox" id="preventAssembly" name="preventAssembly">
<label for="canAssembleDocument" th:text="#{addPassword.selectText.6}"></label> <label for="preventAssembly" th:text="#{addPassword.selectText.6}"></label>
</div> </div>
<div class="form-check ms-3"> <div class="form-check ms-3">
<input type="checkbox" id="canExtractContent" name="canExtractContent"> <input type="checkbox" id="preventExtractContent" name="preventExtractContent">
<label for="canExtractContent" th:text="#{addPassword.selectText.7}"></label> <label for="preventExtractContent" th:text="#{addPassword.selectText.7}"></label>
</div> </div>
<div class="form-check ms-3"> <div class="form-check ms-3">
<input type="checkbox" id="canExtractForAccessibility" name="canExtractForAccessibility"> <input type="checkbox" id="preventExtractForAccessibility" name="preventExtractForAccessibility">
<label for="canExtractForAccessibility" th:text="#{addPassword.selectText.8}"></label> <label for="preventExtractForAccessibility" th:text="#{addPassword.selectText.8}"></label>
</div> </div>
<div class="form-check ms-3"> <div class="form-check ms-3">
<input type="checkbox" id="canFillInForm" name="canFillInForm"> <input type="checkbox" id="preventFillInForm" name="preventFillInForm">
<label for="canFillInForm" th:text="#{addPassword.selectText.9}"></label> <label for="preventFillInForm" th:text="#{addPassword.selectText.9}"></label>
</div> </div>
<div class="form-check ms-3"> <div class="form-check ms-3">
<input type="checkbox" id="canModify" name="canModify"> <input type="checkbox" id="preventModify" name="preventModify">
<label for="canModify" th:text="#{addPassword.selectText.10}"></label> <label for="preventModify" th:text="#{addPassword.selectText.10}"></label>
</div> </div>
<div class="form-check ms-3"> <div class="form-check ms-3">
<input type="checkbox" id="canModifyAnnotations" name="canModifyAnnotations"> <input type="checkbox" id="preventModifyAnnotations" name="preventModifyAnnotations">
<label for="canModifyAnnotations" th:text="#{addPassword.selectText.11}"></label> <label for="preventModifyAnnotations" th:text="#{addPassword.selectText.11}"></label>
</div> </div>
<div class="form-check ms-3"> <div class="form-check ms-3">
<input type="checkbox" id="canPrint" name="canPrint"> <input type="checkbox" id="preventPrinting" name="preventPrinting">
<label for="canPrint" th:text="#{addPassword.selectText.12}"></label> <label for="preventPrinting" th:text="#{addPassword.selectText.12}"></label>
</div> </div>
<div class="form-check ms-3"> <div class="form-check ms-3">
<input type="checkbox" id="canPrintFaithful" name="canPrintFaithful"> <input type="checkbox" id="preventPrintingFaithful" name="preventPrintingFaithful">
<label for="canPrintFaithful" th:text="#{addPassword.selectText.13}"></label> <label for="preventPrintingFaithful" th:text="#{addPassword.selectText.13}"></label>
</div> </div>
</div> </div>
<br> <br>

View File

@ -25,36 +25,36 @@
<div class="mb-3"> <div class="mb-3">
<label class="mb-2" th:text="#{permissions.selectText.2}"></label> <label class="mb-2" th:text="#{permissions.selectText.2}"></label>
<div class="form-check ms-3"> <div class="form-check ms-3">
<input type="checkbox" id="canAssembleDocument" name="canAssembleDocument"> <input type="checkbox" id="preventAssembly" name="preventAssembly">
<label for="canAssembleDocument" th:text="#{permissions.selectText.3}"></label> <label for="preventAssembly" th:text="#{permissions.selectText.3}"></label>
</div> </div>
<div class="form-check ms-3"> <div class="form-check ms-3">
<input type="checkbox" id="canExtractContent" name="canExtractContent"> <input type="checkbox" id="preventExtractContent" name="preventExtractContent">
<label for="canExtractContent" th:text="#{permissions.selectText.4}"></label> <label for="preventExtractContent" th:text="#{permissions.selectText.4}"></label>
</div> </div>
<div class="form-check ms-3"> <div class="form-check ms-3">
<input type="checkbox" id="canExtractForAccessibility" name="canExtractForAccessibility"> <input type="checkbox" id="preventExtractForAccessibility" name="preventExtractForAccessibility">
<label for="canExtractForAccessibility" th:text="#{permissions.selectText.5}"></label> <label for="preventExtractForAccessibility" th:text="#{permissions.selectText.5}"></label>
</div> </div>
<div class="form-check ms-3"> <div class="form-check ms-3">
<input type="checkbox" id="canFillInForm" name="canFillInForm"> <input type="checkbox" id="preventFillInForm" name="preventFillInForm">
<label for="canFillInForm" th:text="#{permissions.selectText.6}"></label> <label for="preventFillInForm" th:text="#{permissions.selectText.6}"></label>
</div> </div>
<div class="form-check ms-3"> <div class="form-check ms-3">
<input type="checkbox" id="canModify" name="canModify"> <input type="checkbox" id="preventModify" name="preventModify">
<label for="canModify" th:text="#{permissions.selectText.7}"></label> <label for="preventModify" th:text="#{permissions.selectText.7}"></label>
</div> </div>
<div class="form-check ms-3"> <div class="form-check ms-3">
<input type="checkbox" id="canModifyAnnotations" name="canModifyAnnotations"> <input type="checkbox" id="preventModifyAnnotations" name="preventModifyAnnotations">
<label for="canModifyAnnotations" th:text="#{permissions.selectText.8}"></label> <label for="preventModifyAnnotations" th:text="#{permissions.selectText.8}"></label>
</div> </div>
<div class="form-check ms-3"> <div class="form-check ms-3">
<input type="checkbox" id="canPrint" name="canPrint"> <input type="checkbox" id="preventPrinting" name="preventPrinting">
<label for="canPrint" th:text="#{permissions.selectText.9}"></label> <label for="preventPrinting" th:text="#{permissions.selectText.9}"></label>
</div> </div>
<div class="form-check ms-3"> <div class="form-check ms-3">
<input type="checkbox" id="canPrintFaithful" name="canPrintFaithful"> <input type="checkbox" id="preventPrintingFaithful" name="preventPrintingFaithful">
<label for="canPrintFaithful" th:text="#{permissions.selectText.10}"></label> <label for="preventPrintingFaithful" th:text="#{permissions.selectText.10}"></label>
</div> </div>
</div> </div>
<br> <br>

View File

@ -19,7 +19,7 @@
<link rel="stylesheet" th:href="@{'/pdfjs-legacy/css/viewer-redact.css'}"> <link rel="stylesheet" th:href="@{'/pdfjs-legacy/css/viewer-redact.css'}">
<script th:src="@{'/pdfjs-legacy/js/viewer.mjs'}" type="module"></script> <script th:src="@{'/pdfjs-legacy/js/viewer.mjs'}" type="module"></script>
<script src='./js/redact.js' type="module"></script> <script th:src="@{'/js/redact.js'}" type="module"></script>
<link rel="stylesheet" th:href="@{'/css/redact.css'}"> <link rel="stylesheet" th:href="@{'/css/redact.css'}">
</head> </head>

View File

@ -11,7 +11,6 @@
# If you want to override with environment parameter follow parameter naming SECURITY_INITIALLOGIN_USERNAME # # If you want to override with environment parameter follow parameter naming SECURITY_INITIALLOGIN_USERNAME #
############################################################################################################# #############################################################################################################
security: security:
enableLogin: false # set to 'true' to enable login enableLogin: false # set to 'true' to enable login
csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production) csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production)
@ -62,18 +61,32 @@ security:
privateKey: classpath:saml-private-key.key # Your private key. Generated from your keypair privateKey: classpath:saml-private-key.key # Your private key. Generated from your keypair
spCert: classpath:saml-public-cert.crt # Your signing certificate. Generated from your keypair spCert: classpath:saml-public-cert.crt # Your signing certificate. Generated from your keypair
enterpriseEdition: premium:
enabled: false # set to 'true' to enable enterprise edition
key: 00000000-0000-0000-0000-000000000000 key: 00000000-0000-0000-0000-000000000000
SSOAutoLogin: false # Enable to auto login to first provided SSO enabled: false # Enable license key checks for pro/enterprise features
proFeatures:
SSOAutoLogin: false
CustomMetadata: CustomMetadata:
autoUpdateMetadata: false # set to 'true' to automatically update metadata with below values autoUpdateMetadata: false # set to 'true' to automatically update metadata with below values
author: username # supports text such as 'John Doe' or types such as username to autopopulate with user's username author: username # supports text such as 'John Doe' or types such as username to autopopulate with user's username
creator: Stirling-PDF # supports text such as 'Company-PDF' creator: Stirling-PDF # supports text such as 'Company-PDF'
producer: Stirling-PDF # supports text such as 'Company-PDF' producer: Stirling-PDF # supports text such as 'Company-PDF'
googleDrive:
enabled: false
clientId: ''
apiKey: ''
appId: ''
mail:
enabled: false # set to 'true' to enable sending emails
host: smtp.example.com # SMTP server hostname
port: 587 # SMTP server port
username: '' # SMTP server username
password: '' # SMTP server password
from: '' # sender email address
legal: legal:
termsAndConditions: https://www.stirlingpdf.com/terms-and-conditions # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder termsAndConditions: https://www.stirlingpdf.com/terms # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder
privacyPolicy: https://www.stirlingpdf.com/privacy-policy # URL to the privacy policy of your application (e.g. https://example.com/privacy). Empty string to disable or filename to load from local file in static folder privacyPolicy: https://www.stirlingpdf.com/privacy-policy # URL to the privacy policy of your application (e.g. https://example.com/privacy). Empty string to disable or filename to load from local file in static folder
accessibilityStatement: '' # URL to the accessibility statement of your application (e.g. https://example.com/accessibility). Empty string to disable or filename to load from local file in static folder accessibilityStatement: '' # URL to the accessibility statement of your application (e.g. https://example.com/accessibility). Empty string to disable or filename to load from local file in static folder
cookiePolicy: '' # URL to the cookie policy of your application (e.g. https://example.com/cookie). Empty string to disable or filename to load from local file in static folder cookiePolicy: '' # URL to the cookie policy of your application (e.g. https://example.com/cookie). Empty string to disable or filename to load from local file in static folder
@ -88,6 +101,7 @@ system:
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template HTML files 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. 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: true # set to 'true' to enable analytics, set to 'false' to disable analytics; for enterprise users, this is set to true enableAnalytics: true # set to 'true' to enable analytics, set to 'false' to disable analytics; for enterprise users, this is set to true
enableUrlToPDF: false # Set to 'true' to enable URL to PDF, INTERNAL ONLY, known security issues, should not be used externally
disableSanitize: false # set to true to disable Sanitize HTML; (can lead to injections in HTML) disableSanitize: false # set to true to disable Sanitize HTML; (can lead to injections in HTML)
datasource: datasource:
enableCustomDatabase: false # Enterprise users ONLY, set this property to 'true' if you would like to use your own custom database configuration enableCustomDatabase: false # Enterprise users ONLY, set this property to 'true' if you would like to use your own custom database configuration
@ -100,13 +114,12 @@ system:
name: postgres # set the name of your database. Should match the name of the database you create name: postgres # set the name of your database. Should match the name of the database you create
customPaths: customPaths:
pipeline: pipeline:
watchedFoldersDir: "" #Defaults to /pipeline/watchedFolders watchedFoldersDir: '' # Defaults to /pipeline/watchedFolders
finishedFoldersDir: "" #Defaults to /pipeline/finishedFolders finishedFoldersDir: '' # Defaults to /pipeline/finishedFolders
operations: operations:
weasyprint: "" #Defaults to /opt/venv/bin/weasyprint weasyprint: '' # Defaults to /opt/venv/bin/weasyprint
unoconvert: "" #Defaults to /opt/venv/bin/unoconvert unoconvert: '' # Defaults to /opt/venv/bin/unoconvert
fileUploadLimit: '' # Defaults to "". No limit when string is empty. Set a number, between 0 and 999, followed by one of the following strings to set a limit. "KB", "MB", "GB".
ui: ui:
appName: '' # application's visible name appName: '' # application's visible name