From 1d89917e885e0d8d5eca9f81a55badb46fa73216 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Aug 2025 21:03:37 +0100 Subject: [PATCH 01/20] build(deps): bump org.springdoc:springdoc-openapi-starter-webmvc-ui from 2.8.9 to 2.8.11 (#4273) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [org.springdoc:springdoc-openapi-starter-webmvc-ui](https://github.com/springdoc/springdoc-openapi) from 2.8.9 to 2.8.11.
Release notes

Sourced from org.springdoc:springdoc-openapi-starter-webmvc-ui's releases.

springdoc-openapi v2.8.11 released!

Added

Changed

Fixed

New Contributors

Full Changelog: https://github.com/springdoc/springdoc-openapi/compare/v2.8.10...v2.8.11

springdoc-openapi v2.8.10 released!

What's Changed

Added

Changed

Fixed

... (truncated)

Changelog

Sourced from org.springdoc:springdoc-openapi-starter-webmvc-ui's changelog.

[2.8.11] - 2025-08-23

Added

Changed

Fixed

[2.8.10] - 2025-08-20

Added

Changed

Fixed

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.springdoc:springdoc-openapi-starter-webmvc-ui&package-manager=gradle&previous-version=2.8.9&new-version=2.8.11)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- app/common/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/common/build.gradle b/app/common/build.gradle index 39dab8ded..6b2d67b32 100644 --- a/app/common/build.gradle +++ b/app/common/build.gradle @@ -39,7 +39,7 @@ dependencies { api "org.apache.pdfbox:pdfbox:$pdfboxVersion" api 'jakarta.servlet:jakarta.servlet-api:6.1.0' api 'org.snakeyaml:snakeyaml-engine:2.10' - api "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9" + api "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.11" api 'jakarta.mail:jakarta.mail-api:2.1.3' runtimeOnly 'org.eclipse.angus:angus-mail:2.0.4' } From ae53492751de7d40a798d20faa0c7019f5cf2d6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Aug 2025 21:03:53 +0100 Subject: [PATCH 02/20] build(deps): bump org.springframework.boot from 3.5.4 to 3.5.5 (#4272) Bumps [org.springframework.boot](https://github.com/spring-projects/spring-boot) from 3.5.4 to 3.5.5.
Release notes

Sourced from org.springframework.boot's releases.

v3.5.5

:lady_beetle: Bug Fixes

  • Hazelcast health indicator reports the wrong status when Hazelcast has shut down due to an out-of-memory error #46909
  • Performance critical tracing code has high overhead due to the use of the Stream API #46844
  • SpringLiquibaseCustomizer is exposed outside its defined visibility scope #46758
  • Race condition in OutputCapture can result in stale data #46721
  • Auto-configured WebClient no longer uses context's ReactorResourceFactory #46673
  • Default value not detected for a field annoted with @Name #46666
  • Missing metadata when using @Name with a constructor-bound property #46663
  • Missing property for Spring Authorization Server's PAR endpoint #46641
  • Property name is incorrect when reporting a mis-configured OAuth 2 Resource Server JWT public key location #46636
  • Memory not freed on context restart in JpaMetamodel#CACHE with spring.main.lazy-initialization=true #46634
  • Auto-configured MockMvc ignores @FilterRegistration annotation #46605
  • Failure to discover default value for a primitive should not lead to document its default value #46561

:notebook_with_decorative_cover: Documentation

  • Kotlin samples for configuration metadata are in the wrong package #46857
  • Observability examples in the reference guide are missing the Kotlin version #46798
  • Align method descriptions for SslOptions getCiphers and getEnabledProtocols with @returns #46769
  • Tracing samples in the reference guide are missing the Kotlin version #46767
  • Improve Virtual Threads section to mention the changes in Java 24 #46610
  • spring.test.webtestclient.timeout is not documented #46588
  • spring-boot-test-autoconfigure should use the configuration properties annotation processor like other modules #46585
  • Adapt deprecation level for management.health.influxdb.enabled #46580
  • spring.test.mockmvc properties are not documented #46578

:hammer: Dependency Upgrades

  • Upgrade to Angus Mail 2.0.4 #46725
  • Upgrade to AssertJ 3.27.4 #46726
  • Upgrade to Byte Buddy 1.17.7 #46883
  • Upgrade to Couchbase Client 3.8.3 #46794
  • Upgrade to Elasticsearch Client 8.18.5 #46830
  • Upgrade to Hibernate 6.6.26.Final #46884
  • Upgrade to Hibernate Validator 8.0.3.Final #46728
  • Upgrade to HikariCP 6.3.2 #46729
  • Upgrade to Jersey 3.1.11 #46730
  • Upgrade to Jetty 12.0.25 #46831
  • Upgrade to Jetty Reactive HTTPClient 4.0.11 #46885
  • Upgrade to jOOQ 3.19.25 #46808
  • Upgrade to MariaDB 3.5.5 #46779
  • Upgrade to Maven Javadoc Plugin 3.11.3 #46886
  • Upgrade to Micrometer 1.15.3 #46701
  • Upgrade to Micrometer Tracing 1.5.3 #46702
  • Upgrade to MySQL 9.4.0 #46732
  • Upgrade to Netty 4.1.124.Final #46832
  • Upgrade to Pulsar 4.0.6 #46733
  • Upgrade to Reactor Bom 2024.0.9 #46703

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.springframework.boot&package-manager=gradle&previous-version=3.5.4&new-version=3.5.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
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 4059d48fe..9c502577a 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id "java" id "jacoco" id "io.spring.dependency-management" version "1.1.7" - id "org.springframework.boot" version "3.5.4" + id "org.springframework.boot" version "3.5.5" id "org.springdoc.openapi-gradle-plugin" version "1.9.0" id "io.swagger.swaggerhub" version "1.3.2" id "edu.sc.seis.launch4j" version "4.0.0" From 0d63bc4a4110396d59becb8231c165d27f70f53a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Aug 2025 21:04:24 +0100 Subject: [PATCH 03/20] build(deps): bump github/codeql-action from 3.29.10 to 3.29.11 (#4271) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.10 to 3.29.11.
Release notes

Sourced from github/codeql-action's releases.

v3.29.11

CodeQL Action Changelog

See the releases page for the relevant changes to the CodeQL CLI and language packs.

3.29.11 - 21 Aug 2025

  • Update default CodeQL bundle version to 2.22.4. #3044

See the full CHANGELOG.md for more information.

Changelog

Sourced from github/codeql-action's changelog.

CodeQL Action Changelog

See the releases page for the relevant changes to the CodeQL CLI and language packs.

[UNRELEASED]

No user facing changes.

3.29.11 - 21 Aug 2025

  • Update default CodeQL bundle version to 2.22.4. #3044

3.29.10 - 18 Aug 2025

No user facing changes.

3.29.9 - 12 Aug 2025

No user facing changes.

3.29.8 - 08 Aug 2025

  • Fix an issue where the Action would autodetect unsupported languages such as HTML. #3015

3.29.7 - 07 Aug 2025

This release rolls back 3.29.6 to address issues with language autodetection. It is identical to 3.29.5.

3.29.6 - 07 Aug 2025

  • The cleanup-level input to the analyze Action is now deprecated. The CodeQL Action has written a limited amount of intermediate results to the database since version 2.2.5, and now automatically manages cleanup. #2999
  • Update default CodeQL bundle version to 2.22.3. #3000

3.29.5 - 29 Jul 2025

  • Update default CodeQL bundle version to 2.22.2. #2986

3.29.4 - 23 Jul 2025

No user facing changes.

3.29.3 - 21 Jul 2025

No user facing changes.

3.29.2 - 30 Jun 2025

  • Experimental: When the quality-queries input for the init action is provided with an argument, separate .quality.sarif files are produced and uploaded for each language with the results of the specified queries. Do not use this in production as it is part of an internal experiment and subject to change at any time. #2935

3.29.1 - 27 Jun 2025

... (truncated)

Commits
  • 3c3833e Merge pull request #3052 from github/update-v3.29.11-14148a433
  • 8c4bfbd Update changelog for v3.29.11
  • 14148a4 Merge pull request #3044 from github/update-bundle/codeql-bundle-v2.22.4
  • 71b2cb3 Add changelog note
  • 2bf7825 Update default bundle to codeql-bundle-v2.22.4
  • db69a51 Merge pull request #3049 from github/update-supported-enterprise-server-versions
  • a68d47b Merge pull request #3050 from github/henrymercer/init-not-called-config-error
  • e496ff9 Make "init not called" a configuration error
  • fd2ea72 Update supported GitHub Enterprise Server versions
  • 6dee5bc Merge pull request #3045 from github/dependabot/npm_and_yarn/npm-5b4171dd16
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action&package-manager=github_actions&previous-version=3.29.10&new-version=3.29.11)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 53ad28c84..8dc3e94f3 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -74,6 +74,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.5 + uses: github/codeql-action/upload-sarif@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.5 with: sarif_file: results.sarif From 44dbeebd40352a3d306173a00f3e3adbfd8a22e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Aug 2025 21:05:37 +0100 Subject: [PATCH 04/20] build(deps): bump org.springframework.boot:spring-boot-dependencies from 3.5.4 to 3.5.5 (#4268) Bumps [org.springframework.boot:spring-boot-dependencies](https://github.com/spring-projects/spring-boot) from 3.5.4 to 3.5.5.
Release notes

Sourced from org.springframework.boot:spring-boot-dependencies's releases.

v3.5.5

:lady_beetle: Bug Fixes

  • Hazelcast health indicator reports the wrong status when Hazelcast has shut down due to an out-of-memory error #46909
  • Performance critical tracing code has high overhead due to the use of the Stream API #46844
  • SpringLiquibaseCustomizer is exposed outside its defined visibility scope #46758
  • Race condition in OutputCapture can result in stale data #46721
  • Auto-configured WebClient no longer uses context's ReactorResourceFactory #46673
  • Default value not detected for a field annoted with @Name #46666
  • Missing metadata when using @Name with a constructor-bound property #46663
  • Missing property for Spring Authorization Server's PAR endpoint #46641
  • Property name is incorrect when reporting a mis-configured OAuth 2 Resource Server JWT public key location #46636
  • Memory not freed on context restart in JpaMetamodel#CACHE with spring.main.lazy-initialization=true #46634
  • Auto-configured MockMvc ignores @FilterRegistration annotation #46605
  • Failure to discover default value for a primitive should not lead to document its default value #46561

:notebook_with_decorative_cover: Documentation

  • Kotlin samples for configuration metadata are in the wrong package #46857
  • Observability examples in the reference guide are missing the Kotlin version #46798
  • Align method descriptions for SslOptions getCiphers and getEnabledProtocols with @returns #46769
  • Tracing samples in the reference guide are missing the Kotlin version #46767
  • Improve Virtual Threads section to mention the changes in Java 24 #46610
  • spring.test.webtestclient.timeout is not documented #46588
  • spring-boot-test-autoconfigure should use the configuration properties annotation processor like other modules #46585
  • Adapt deprecation level for management.health.influxdb.enabled #46580
  • spring.test.mockmvc properties are not documented #46578

:hammer: Dependency Upgrades

  • Upgrade to Angus Mail 2.0.4 #46725
  • Upgrade to AssertJ 3.27.4 #46726
  • Upgrade to Byte Buddy 1.17.7 #46883
  • Upgrade to Couchbase Client 3.8.3 #46794
  • Upgrade to Elasticsearch Client 8.18.5 #46830
  • Upgrade to Hibernate 6.6.26.Final #46884
  • Upgrade to Hibernate Validator 8.0.3.Final #46728
  • Upgrade to HikariCP 6.3.2 #46729
  • Upgrade to Jersey 3.1.11 #46730
  • Upgrade to Jetty 12.0.25 #46831
  • Upgrade to Jetty Reactive HTTPClient 4.0.11 #46885
  • Upgrade to jOOQ 3.19.25 #46808
  • Upgrade to MariaDB 3.5.5 #46779
  • Upgrade to Maven Javadoc Plugin 3.11.3 #46886
  • Upgrade to Micrometer 1.15.3 #46701
  • Upgrade to Micrometer Tracing 1.5.3 #46702
  • Upgrade to MySQL 9.4.0 #46732
  • Upgrade to Netty 4.1.124.Final #46832
  • Upgrade to Pulsar 4.0.6 #46733
  • Upgrade to Reactor Bom 2024.0.9 #46703

... (truncated)

Commits
  • 3537d25 Release v3.5.5
  • a22e28e Merge branch '3.4.x' into 3.5.x
  • 4cb8c8a Next development version (v3.4.10-SNAPSHOT)
  • 9d205e2 Merge branch '3.4.x' into 3.5.x
  • 47b0632 Merge pull request #46927 from izeye
  • 8b71458 Adapt checkstyle rules for 3.4.x
  • fb99bad Remove redundant suppressions from Checkstyle configuration
  • 8af836a Upgrade to Spring RESTDocs 3.0.5
  • ae6c6a5 Merge branch '3.4.x' into 3.5.x
  • b6bae9f Upgrade to Spring RESTDocs 3.0.5
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.springframework.boot:spring-boot-dependencies&package-manager=gradle&previous-version=3.5.4&new-version=3.5.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
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 9c502577a..5c6893cdf 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ import java.nio.file.Files import java.time.Year ext { - springBootVersion = "3.5.4" + springBootVersion = "3.5.5" pdfboxVersion = "3.0.5" imageioVersion = "3.12.0" lombokVersion = "1.18.38" From 73d419cb39c0df9f2965ea68d094dce84c298528 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Aug 2025 21:16:57 +0100 Subject: [PATCH 05/20] build(deps): bump springSecuritySamlVersion from 6.5.2 to 6.5.3 (#4227) Bumps `springSecuritySamlVersion` from 6.5.2 to 6.5.3. Updates `org.springframework.security:spring-security-core` from 6.5.2 to 6.5.3
Release notes

Sourced from org.springframework.security:spring-security-core's releases.

6.5.3

:star: New Features

  • Add META-INF/LICENSE.txt to published jars #17639
  • Update Angular documentation links in csrf.adoc #17653
  • Update Shibboleth Repository URL #17637
  • Use 2004-present Copyright #17634

:beetle: Bug Fixes

  • Add Missing Navigation in Preparing for 7.0 Guide #17731
  • DPoP authentication throws JwtDecoderFactory ClassNotFoundException #17249
  • OpenSamlAssertingPartyDetails Should Be Serializable #17727
  • Use final values in equals and hashCode #17621

:hammer: Dependency Upgrades

  • Bump com.webauthn4j:webauthn4j-core from 0.29.4.RELEASE to 0.29.5.RELEASE #17739
  • Bump com.webauthn4j:webauthn4j-core from 0.29.4.RELEASE to 0.29.5.RELEASE #17690
  • Bump com.webauthn4j:webauthn4j-core from 0.29.4.RELEASE to 0.29.5.RELEASE #17684
  • Bump com.webauthn4j:webauthn4j-core from 0.29.4.RELEASE to 0.29.5.RELEASE #17661
  • Bump io.micrometer:micrometer-observation from 1.14.8 to 1.14.9 #17615
  • Bump io.micrometer:micrometer-observation from 1.14.8 to 1.14.9 #17599
  • Bump io.micrometer:micrometer-observation from 1.14.9 to 1.14.10 #17737
  • Bump io.micrometer:micrometer-observation from 1.14.9 to 1.14.10 #17701
  • Bump io.mockk:mockk from 1.14.4 to 1.14.5 #17614
  • Bump io.spring.develocity.conventions from 0.0.23 to 0.0.24 #17647
  • Bump io.spring.gradle:spring-security-release-plugin from 1.0.10 to 1.0.11 #17733
  • Bump io.spring.gradle:spring-security-release-plugin from 1.0.10 to 1.0.11 #17711
  • Bump io.spring.gradle:spring-security-release-plugin from 1.0.6 to 1.0.10 #17612
  • Bump io.spring.gradle:spring-security-release-plugin from 1.0.6 to 1.0.10 #17598
  • Bump org-eclipse-jetty from 11.0.25 to 11.0.26 #17742
  • Bump org.apache.maven:maven-resolver-provider from 3.9.10 to 3.9.11 #17613
  • Bump org.apache.maven:maven-resolver-provider from 3.9.10 to 3.9.11 #17595
  • Bump org.assertj:assertj-core from 3.27.3 to 3.27.4 #17760
  • Bump org.assertj:assertj-core from 3.27.3 to 3.27.4 #17692
  • Bump org.assertj:assertj-core from 3.27.3 to 3.27.4 #17683
  • Bump org.assertj:assertj-core from 3.27.3 to 3.27.4 #17671
  • Bump org.gretty:gretty from 4.1.6 to 4.1.7 #17616
  • Bump org.gretty:gretty from 4.1.6 to 4.1.7 #17597
  • Bump org.hibernate.orm:hibernate-core from 6.6.20.Final to 6.6.23.Final #17646
  • Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.24.Final #17660
  • Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.25.Final #17694
  • Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.25.Final #17685
  • Bump org.jfrog.buildinfo:build-info-extractor-gradle from 4.34.1 to 4.34.2 #17650
  • Bump org.springframework.data:spring-data-bom from 2024.1.7 to 2024.1.8 #17645
  • Bump org.springframework.ldap:spring-ldap-core from 3.2.13 to 3.2.14 #17757
  • Bump org.springframework:spring-framework-bom from 6.2.8 to 6.2.9 #17651
  • Bump org.springframework:spring-framework-bom from 6.2.8 to 6.2.9 #17596
  • Bump org.springframework:spring-framework-bom from 6.2.9 to 6.2.10 #17735

... (truncated)

Commits
  • 44037c0 Release 6.5.3
  • 9909dc6 Merge branch '6.4.x' into 6.5.x
  • 525601e Fix version 6.4.9-SNAPSHOT
  • 15a4d0d Fix version=6.5.3-SNAPSHOT
  • 80b1a30 Merge branch '6.4.x' into 6.5.x
  • 644f780 Bump org.springframework.ldap:spring-ldap-core from 3.2.13 to 3.2.14
  • a26d6fc Bump org.springframework.data:spring-data-bom from 2024.1.8 to 2024.1.9
  • 74735a1 Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.26.Final
  • 82a16d7 Bump org.assertj:assertj-core from 3.27.3 to 3.27.4
  • c1869c1 Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.26.Final
  • Additional commits viewable in compare view

Updates `org.springframework.security:spring-security-saml2-service-provider` from 6.5.2 to 6.5.3
Release notes

Sourced from org.springframework.security:spring-security-saml2-service-provider's releases.

6.5.3

:star: New Features

  • Add META-INF/LICENSE.txt to published jars #17639
  • Update Angular documentation links in csrf.adoc #17653
  • Update Shibboleth Repository URL #17637
  • Use 2004-present Copyright #17634

:beetle: Bug Fixes

  • Add Missing Navigation in Preparing for 7.0 Guide #17731
  • DPoP authentication throws JwtDecoderFactory ClassNotFoundException #17249
  • OpenSamlAssertingPartyDetails Should Be Serializable #17727
  • Use final values in equals and hashCode #17621

:hammer: Dependency Upgrades

  • Bump com.webauthn4j:webauthn4j-core from 0.29.4.RELEASE to 0.29.5.RELEASE #17739
  • Bump com.webauthn4j:webauthn4j-core from 0.29.4.RELEASE to 0.29.5.RELEASE #17690
  • Bump com.webauthn4j:webauthn4j-core from 0.29.4.RELEASE to 0.29.5.RELEASE #17684
  • Bump com.webauthn4j:webauthn4j-core from 0.29.4.RELEASE to 0.29.5.RELEASE #17661
  • Bump io.micrometer:micrometer-observation from 1.14.8 to 1.14.9 #17615
  • Bump io.micrometer:micrometer-observation from 1.14.8 to 1.14.9 #17599
  • Bump io.micrometer:micrometer-observation from 1.14.9 to 1.14.10 #17737
  • Bump io.micrometer:micrometer-observation from 1.14.9 to 1.14.10 #17701
  • Bump io.mockk:mockk from 1.14.4 to 1.14.5 #17614
  • Bump io.spring.develocity.conventions from 0.0.23 to 0.0.24 #17647
  • Bump io.spring.gradle:spring-security-release-plugin from 1.0.10 to 1.0.11 #17733
  • Bump io.spring.gradle:spring-security-release-plugin from 1.0.10 to 1.0.11 #17711
  • Bump io.spring.gradle:spring-security-release-plugin from 1.0.6 to 1.0.10 #17612
  • Bump io.spring.gradle:spring-security-release-plugin from 1.0.6 to 1.0.10 #17598
  • Bump org-eclipse-jetty from 11.0.25 to 11.0.26 #17742
  • Bump org.apache.maven:maven-resolver-provider from 3.9.10 to 3.9.11 #17613
  • Bump org.apache.maven:maven-resolver-provider from 3.9.10 to 3.9.11 #17595
  • Bump org.assertj:assertj-core from 3.27.3 to 3.27.4 #17760
  • Bump org.assertj:assertj-core from 3.27.3 to 3.27.4 #17692
  • Bump org.assertj:assertj-core from 3.27.3 to 3.27.4 #17683
  • Bump org.assertj:assertj-core from 3.27.3 to 3.27.4 #17671
  • Bump org.gretty:gretty from 4.1.6 to 4.1.7 #17616
  • Bump org.gretty:gretty from 4.1.6 to 4.1.7 #17597
  • Bump org.hibernate.orm:hibernate-core from 6.6.20.Final to 6.6.23.Final #17646
  • Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.24.Final #17660
  • Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.25.Final #17694
  • Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.25.Final #17685
  • Bump org.jfrog.buildinfo:build-info-extractor-gradle from 4.34.1 to 4.34.2 #17650
  • Bump org.springframework.data:spring-data-bom from 2024.1.7 to 2024.1.8 #17645
  • Bump org.springframework.ldap:spring-ldap-core from 3.2.13 to 3.2.14 #17757
  • Bump org.springframework:spring-framework-bom from 6.2.8 to 6.2.9 #17651
  • Bump org.springframework:spring-framework-bom from 6.2.8 to 6.2.9 #17596
  • Bump org.springframework:spring-framework-bom from 6.2.9 to 6.2.10 #17735

... (truncated)

Commits
  • 44037c0 Release 6.5.3
  • 9909dc6 Merge branch '6.4.x' into 6.5.x
  • 525601e Fix version 6.4.9-SNAPSHOT
  • 15a4d0d Fix version=6.5.3-SNAPSHOT
  • 80b1a30 Merge branch '6.4.x' into 6.5.x
  • 644f780 Bump org.springframework.ldap:spring-ldap-core from 3.2.13 to 3.2.14
  • a26d6fc Bump org.springframework.data:spring-data-bom from 2024.1.8 to 2024.1.9
  • 74735a1 Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.26.Final
  • 82a16d7 Bump org.assertj:assertj-core from 3.27.3 to 3.27.4
  • c1869c1 Bump org.hibernate.orm:hibernate-core from 6.6.23.Final to 6.6.26.Final
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
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 5c6893cdf..4016c2bc6 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ ext { imageioVersion = "3.12.0" lombokVersion = "1.18.38" bouncycastleVersion = "1.81" - springSecuritySamlVersion = "6.5.2" + springSecuritySamlVersion = "6.5.3" openSamlVersion = "4.3.2" commonmarkVersion = "0.25.1" googleJavaFormatVersion = "1.28.0" From 3f004dcad328a887084d1957c483e85d6aa26b48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Aug 2025 21:17:10 +0100 Subject: [PATCH 06/20] build(deps): bump io.swagger.core.v3:swagger-core-jakarta from 2.2.35 to 2.2.36 (#4226) Bumps io.swagger.core.v3:swagger-core-jakarta from 2.2.35 to 2.2.36. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=io.swagger.core.v3:swagger-core-jakarta&package-manager=gradle&previous-version=2.2.35&new-version=2.2.36)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- app/proprietary/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/proprietary/build.gradle b/app/proprietary/build.gradle index 0254d63ed..2c9058729 100644 --- a/app/proprietary/build.gradle +++ b/app/proprietary/build.gradle @@ -49,7 +49,7 @@ dependencies { api 'org.springframework.boot:spring-boot-starter-mail' api 'org.springframework.boot:spring-boot-starter-cache' api 'com.github.ben-manes.caffeine:caffeine' - api 'io.swagger.core.v3:swagger-core-jakarta:2.2.35' + api 'io.swagger.core.v3:swagger-core-jakarta:2.2.36' implementation 'com.bucket4j:bucket4j_jdk17-core:8.14.0' // https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17 From 2baa258e1106e43538d1a4c8a713d7c39ee239b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Aug 2025 21:17:32 +0100 Subject: [PATCH 07/20] build(deps): bump io.micrometer:micrometer-core from 1.15.2 to 1.15.3 (#4190) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [//]: # (dependabot-start) ⚠️ **Dependabot is rebasing this PR** ⚠️ Rebasing might not happen immediately, so don't worry if this takes some time. Note: if you make any changes to this PR yourself, they will take precedence over the rebase. --- [//]: # (dependabot-end) Bumps [io.micrometer:micrometer-core](https://github.com/micrometer-metrics/micrometer) from 1.15.2 to 1.15.3.
Release notes

Sourced from io.micrometer:micrometer-core's releases.

1.15.3

:lady_beetle: Bug Fixes

  • Catch IllegalArgumentException in VirtualThreadMetrics #6584
  • Handle ArrayIndexOutOfBoundsException from DoubleHistogram in TimeWindowPercentileHistogram.accumulate() defensively #6563
  • Sync OutputCapture from Spring Boot #6608

:hammer: Dependency Upgrades

  • Bump jersey3 from 3.1.10 to 3.1.11 #6607
  • Bump com.netflix.spectator:spectator-reg-atlas from 1.8.16 to 1.8.17 #6600
  • Bump io.netty:netty-bom from 4.1.122.Final to 4.1.123.Final #6537

:heart: Contributors

Thank you to all the contributors who worked on this release:

@​genuss and @​izeye

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=io.micrometer:micrometer-core&package-manager=gradle&previous-version=1.15.2&new-version=1.15.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- app/core/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/core/build.gradle b/app/core/build.gradle index 409e1711d..87c80b611 100644 --- a/app/core/build.gradle +++ b/app/core/build.gradle @@ -58,7 +58,7 @@ dependencies { implementation 'commons-io:commons-io:2.20.0' implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion" implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion" - implementation 'io.micrometer:micrometer-core:1.15.2' + implementation 'io.micrometer:micrometer-core:1.15.3' implementation 'com.google.zxing:core:3.5.3' implementation "org.commonmark:commonmark:$commonmarkVersion" // https://mvnrepository.com/artifact/org.commonmark/commonmark implementation "org.commonmark:commonmark-ext-gfm-tables:$commonmarkVersion" From 9779c75df4655b76bad01d2191fae08b8ed38ea6 Mon Sep 17 00:00:00 2001 From: Ludy Date: Sun, 24 Aug 2025 22:20:28 +0200 Subject: [PATCH 08/20] refactor(tests): move & expand TextFinder/RedactController tests; fix TextFinder empty search-term handling; update token filtering API (#4264) # Description of Changes - **What was changed** - Relocated and refactored unit tests: - `TextFinderTest` and `RedactControllerTest` moved under `app/core/src/test/...` to align with module structure. - Expanded test coverage: whole-word vs. partial matches, complex regexes (emails, SSNs, IPs, currency), international/accented characters, multi-page documents, malformed PDFs, operator preservation, color decoding, and performance assertions. - **API adjustments in redaction flow**: - `createTokensWithoutTargetText(...)` now accepts the `PDDocument` alongside `PDPage` to properly manage resources/streams. - Introduced/used `createPlaceholderWithFont(...)` to maintain text width with explicit font context. - **Bug fix in `TextFinder`**: - Early-return when the (trimmed) search term is empty to prevent unnecessary processing and avoid false positives/errors. - Minor cleanup (removed redundant `super()` call) and improved guard logic around regex/whole-word wrapping. - **Why the change was made** - Improve reliability and determinism of PDF redaction and text finding by exercising real-world patterns and edge cases. - Ensure structural PDF operators (graphics/positioning) are preserved during token filtering. - Prevent crashes or misleading matches when users provide empty/whitespace-only search terms. - Align tests with the current project layout and increase maintainability. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --- .../software/SPDF/pdf/TextFinder.java | 7 +- .../api/security/RedactControllerTest.java | 362 +++++++++++------- .../software/SPDF/pdf/TextFinderTest.java | 284 +++++++++----- 3 files changed, 419 insertions(+), 234 deletions(-) rename {stirling-pdf => app/core}/src/test/java/stirling/software/SPDF/controller/api/security/RedactControllerTest.java (78%) rename {stirling-pdf => app/core}/src/test/java/stirling/software/SPDF/pdf/TextFinderTest.java (69%) diff --git a/app/core/src/main/java/stirling/software/SPDF/pdf/TextFinder.java b/app/core/src/main/java/stirling/software/SPDF/pdf/TextFinder.java index 432fad101..c99a2ade7 100644 --- a/app/core/src/main/java/stirling/software/SPDF/pdf/TextFinder.java +++ b/app/core/src/main/java/stirling/software/SPDF/pdf/TextFinder.java @@ -27,7 +27,6 @@ public class TextFinder extends PDFTextStripper { public TextFinder(String searchTerm, boolean useRegex, boolean wholeWordSearch) throws IOException { - super(); this.searchTerm = searchTerm; this.useRegex = useRegex; this.wholeWordSearch = wholeWordSearch; @@ -68,11 +67,15 @@ public class TextFinder extends PDFTextStripper { } String processedSearchTerm = this.searchTerm.trim(); + if (processedSearchTerm.isEmpty()) { + super.endPage(page); + return; + } String regex = this.useRegex ? processedSearchTerm : "\\Q" + processedSearchTerm + "\\E"; if (this.wholeWordSearch) { if (processedSearchTerm.length() == 1 && Character.isDigit(processedSearchTerm.charAt(0))) { - regex = "(? redactionAreas, boolean convertToImage) throws Exception { + private void testManualRedaction(List redactionAreas, boolean convertToImage) + throws Exception { ManualRedactPdfRequest request = createManualRedactPdfRequest(); request.setRedactions(redactionAreas); request.setConvertPDFToImage(convertToImage); @@ -123,18 +128,16 @@ class RedactControllerTest { @BeforeEach void setUp() throws IOException { - mockPdfFile = new MockMultipartFile( - "fileInput", - "test.pdf", - "application/pdf", - createSimplePdfContent() - ); + mockPdfFile = + new MockMultipartFile( + "fileInput", "test.pdf", "application/pdf", createSimplePdfContent()); // Mock PDF document and related objects mockDocument = mock(PDDocument.class); mockPages = mock(PDPageTree.class); mockPage = mock(PDPage.class); - org.apache.pdfbox.pdmodel.PDDocumentCatalog mockCatalog = mock(org.apache.pdfbox.pdmodel.PDDocumentCatalog.class); + org.apache.pdfbox.pdmodel.PDDocumentCatalog mockCatalog = + mock(org.apache.pdfbox.pdmodel.PDDocumentCatalog.class); // Setup document structure properly when(pdfDocumentFactory.load(any(MockMultipartFile.class))).thenReturn(mockDocument); @@ -153,12 +156,14 @@ class RedactControllerTest { when(mockPage.getMediaBox()).thenReturn(pageRect); when(mockPage.getBBox()).thenReturn(pageRect); - InputStream mockInputStream = new ByteArrayInputStream("BT /F1 12 Tf 100 200 Td (test content) Tj ET".getBytes()); + InputStream mockInputStream = + new ByteArrayInputStream("BT /F1 12 Tf 100 200 Td (test content) Tj ET".getBytes()); when(mockPage.getContents()).thenReturn(mockInputStream); when(mockPage.hasContents()).thenReturn(true); - org.apache.pdfbox.cos.COSDocument mockCOSDocument = mock(org.apache.pdfbox.cos.COSDocument.class); + org.apache.pdfbox.cos.COSDocument mockCOSDocument = + mock(org.apache.pdfbox.cos.COSDocument.class); org.apache.pdfbox.cos.COSStream mockCOSStream = mock(org.apache.pdfbox.cos.COSStream.class); when(mockDocument.getDocument()).thenReturn(mockCOSDocument); when(mockCOSDocument.createCOSStream()).thenReturn(mockCOSStream); @@ -167,11 +172,14 @@ class RedactControllerTest { when(mockCOSStream.createOutputStream()).thenReturn(mockOutputStream); when(mockCOSStream.createOutputStream(any())).thenReturn(mockOutputStream); - doAnswer(invocation -> { - ByteArrayOutputStream baos = invocation.getArgument(0); - baos.write("Mock PDF Content".getBytes()); - return null; - }).when(mockDocument).save(any(ByteArrayOutputStream.class)); + doAnswer( + invocation -> { + ByteArrayOutputStream baos = invocation.getArgument(0); + baos.write("Mock PDF Content".getBytes()); + return null; + }) + .when(mockDocument) + .save(any(ByteArrayOutputStream.class)); doNothing().when(mockDocument).close(); // Initialize a real document for unit tests @@ -185,7 +193,8 @@ class RedactControllerTest { // Set up basic page resources PDResources resources = new PDResources(); - resources.put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA)); + resources.put( + COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA)); realPage.setResources(resources); } @@ -222,7 +231,14 @@ class RedactControllerTest { @Test @DisplayName("Should redact multiple search terms") void redactMultipleSearchTerms() throws Exception { - testAutoRedaction("confidential\nsecret\nprivate\nclassified", false, true, "#FF0000", 2.0f, false, true); + testAutoRedaction( + "confidential\nsecret\nprivate\nclassified", + false, + true, + "#FF0000", + 2.0f, + false, + true); } @Test @@ -250,8 +266,12 @@ class RedactControllerTest { when(page.getBBox()).thenReturn(pageRect); when(page.hasContents()).thenReturn(true); - InputStream mockInputStream = new ByteArrayInputStream( - ("BT /F1 12 Tf 100 200 Td (page " + i + " content with confidential info) Tj ET").getBytes()); + InputStream mockInputStream = + new ByteArrayInputStream( + ("BT /F1 12 Tf 100 200 Td (page " + + i + + " content with confidential info) Tj ET") + .getBytes()); when(page.getContents()).thenReturn(mockInputStream); pageList.add(page); @@ -285,7 +305,8 @@ class RedactControllerTest { when(mockPages.get(0)).thenReturn(mockPage); - org.apache.pdfbox.pdmodel.PDDocumentInformation mockInfo = mock(org.apache.pdfbox.pdmodel.PDDocumentInformation.class); + org.apache.pdfbox.pdmodel.PDDocumentInformation mockInfo = + mock(org.apache.pdfbox.pdmodel.PDDocumentInformation.class); when(mockDocument.getDocumentInformation()).thenReturn(mockInfo); ResponseEntity response = redactController.redactPdf(request); @@ -311,23 +332,27 @@ class RedactControllerTest { @Test @DisplayName("Should handle email pattern redaction") void handleEmailPatternRedaction() throws Exception { - testAutoRedaction("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", true, false, "#0000FF", 1.5f, false, true); + testAutoRedaction( + "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", + true, false, "#0000FF", 1.5f, false, true); } @Test @DisplayName("Should handle phone number patterns") void handlePhoneNumberPatterns() throws Exception { - testAutoRedaction("\\(\\d{3}\\)\\s*\\d{3}-\\d{4}", true, false, "#FF0000", 1.0f, false, true); + testAutoRedaction( + "\\(\\d{3}\\)\\s*\\d{3}-\\d{4}", true, false, "#FF0000", 1.0f, false, true); } @ParameterizedTest - @ValueSource(strings = { - "\\d{3}-\\d{2}-\\d{4}", // SSN pattern - "\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}", // Credit card pattern - "\\b[A-Z]{2,}\\b", // Uppercase words - "\\$\\d+\\.\\d{2}", // Currency pattern - "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b" // IP address pattern - }) + @ValueSource( + strings = { + "\\d{3}-\\d{2}-\\d{4}", // SSN pattern + "\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}", // Credit card pattern + "\\b[A-Z]{2,}\\b", // Uppercase words + "\\$\\d+\\.\\d{2}", // Currency pattern + "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b" // IP address pattern + }) @DisplayName("Should handle various regex patterns") void handleVariousRegexPatterns(String regexPattern) throws Exception { testAutoRedaction(regexPattern, true, false, "#000000", 1.0f, false, true); @@ -519,8 +544,10 @@ class RedactControllerTest { when(page.getBBox()).thenReturn(pageRect); when(page.hasContents()).thenReturn(true); - InputStream mockInputStream = new ByteArrayInputStream( - ("BT /F1 12 Tf 100 200 Td (page " + i + " content) Tj ET").getBytes()); + InputStream mockInputStream = + new ByteArrayInputStream( + ("BT /F1 12 Tf 100 200 Td (page " + i + " content) Tj ET") + .getBytes()); when(page.getContents()).thenReturn(mockInputStream); pageList.add(page); @@ -588,36 +615,38 @@ class RedactControllerTest { request.setFileInput(null); request.setListOfText("test"); - assertDoesNotThrow(() -> { - try { - redactController.redactPdf(request); - } catch (Exception e) { - assertNotNull(e); - } - }); + assertDoesNotThrow( + () -> { + try { + redactController.redactPdf(request); + } catch (Exception e) { + assertNotNull(e); + } + }); } @Test @DisplayName("Should handle malformed PDF gracefully") void handleMalformedPdfGracefully() throws Exception { - MockMultipartFile malformedFile = new MockMultipartFile( - "fileInput", - "malformed.pdf", - "application/pdf", - "Not a real PDF content".getBytes() - ); + MockMultipartFile malformedFile = + new MockMultipartFile( + "fileInput", + "malformed.pdf", + "application/pdf", + "Not a real PDF content".getBytes()); RedactPdfRequest request = new RedactPdfRequest(); request.setFileInput(malformedFile); request.setListOfText("test"); - assertDoesNotThrow(() -> { - try { - redactController.redactPdf(request); - } catch (Exception e) { - assertNotNull(e); - } - }); + assertDoesNotThrow( + () -> { + try { + redactController.redactPdf(request); + } catch (Exception e) { + assertNotNull(e); + } + }); } @Test @@ -723,14 +752,24 @@ class RedactControllerTest { } @ParameterizedTest - @ValueSource(strings = {"#FF0000", "#00FF00", "#0000FF", "#FFFFFF", "#000000", "FF0000", "00FF00", "0000FF"}) + @ValueSource( + strings = { + "#FF0000", "#00FF00", "#0000FF", "#FFFFFF", "#000000", "FF0000", "00FF00", + "0000FF" + }) @DisplayName("Should handle various valid color formats") void handleVariousValidColorFormats(String colorInput) throws Exception { Color result = redactController.decodeOrDefault(colorInput); assertNotNull(result); - assertTrue(result.getRed() >= 0 && result.getRed() <= 255, "Red component should be in valid range"); - assertTrue(result.getGreen() >= 0 && result.getGreen() <= 255, "Green component should be in valid range"); - assertTrue(result.getBlue() >= 0 && result.getBlue() <= 255, "Blue component should be in valid range"); + assertTrue( + result.getRed() >= 0 && result.getRed() <= 255, + "Red component should be in valid range"); + assertTrue( + result.getGreen() >= 0 && result.getGreen() <= 255, + "Green component should be in valid range"); + assertTrue( + result.getBlue() >= 0 && result.getBlue() <= 255, + "Blue component should be in valid range"); } @Test @@ -755,16 +794,18 @@ class RedactControllerTest { Set targetWords = Set.of("confidential"); - List tokens = redactController.createTokensWithoutTargetText(realPage, targetWords, false, false); + List tokens = + redactController.createTokensWithoutTargetText( + realDocument, realPage, targetWords, false, false); assertNotNull(tokens); assertFalse(tokens.isEmpty()); String reconstructedText = extractTextFromTokens(tokens); - assertFalse(reconstructedText.contains("confidential"), - "Target text should be replaced with placeholder"); - assertTrue(reconstructedText.contains("document"), - "Non-target text should remain"); + assertFalse( + reconstructedText.contains("confidential"), + "Target text should be replaced with placeholder"); + assertTrue(reconstructedText.contains("document"), "Non-target text should remain"); } @Test @@ -774,7 +815,9 @@ class RedactControllerTest { Set targetWords = Set.of("secret"); - List tokens = redactController.createTokensWithoutTargetText(realPage, targetWords, false, false); + List tokens = + redactController.createTokensWithoutTargetText( + realDocument, realPage, targetWords, false, false); assertNotNull(tokens); @@ -785,7 +828,9 @@ class RedactControllerTest { if (array.getObject(i) instanceof COSString cosString) { String text = cosString.getString(); if (text.contains("secret")) { - fail("Target text 'secret' should have been redacted from TJ array"); + fail( + "Target text 'secret' should have been redacted from TJ" + + " array"); } foundModifiedTJArray = true; } @@ -803,21 +848,33 @@ class RedactControllerTest { Set targetWords = Set.of("redact"); List originalTokens = getOriginalTokens(); - List filteredTokens = redactController.createTokensWithoutTargetText(realPage, targetWords, false, false); + List filteredTokens = + redactController.createTokensWithoutTargetText( + realDocument, realPage, targetWords, false, false); - long originalNonTextCount = originalTokens.stream() - .filter(token -> token instanceof Operator op && !redactController.isTextShowingOperator(op.getName())) - .count(); + long originalNonTextCount = + originalTokens.stream() + .filter( + token -> + token instanceof Operator op + && !redactController.isTextShowingOperator( + op.getName())) + .count(); - long filteredNonTextCount = filteredTokens.stream() - .filter(token -> token instanceof Operator op && !redactController.isTextShowingOperator(op.getName())) - .count(); + long filteredNonTextCount = + filteredTokens.stream() + .filter( + token -> + token instanceof Operator op + && !redactController.isTextShowingOperator( + op.getName())) + .count(); - assertTrue(filteredNonTextCount > 0, - "Non-text operators should be preserved"); + assertTrue(filteredNonTextCount > 0, "Non-text operators should be preserved"); - assertTrue(filteredNonTextCount >= originalNonTextCount / 2, - "A reasonable number of non-text operators should be preserved"); + assertTrue( + filteredNonTextCount >= originalNonTextCount / 2, + "A reasonable number of non-text operators should be preserved"); } @Test @@ -827,7 +884,9 @@ class RedactControllerTest { Set targetWords = Set.of("\\d{3}-\\d{2}-\\d{4}"); // SSN pattern - List tokens = redactController.createTokensWithoutTargetText(realPage, targetWords, true, false); + List tokens = + redactController.createTokensWithoutTargetText( + realDocument, realPage, targetWords, true, false); String reconstructedText = extractTextFromTokens(tokens); assertFalse(reconstructedText.contains("111-22-3333"), "SSN should be redacted"); @@ -841,7 +900,9 @@ class RedactControllerTest { Set targetWords = Set.of("test"); - List tokens = redactController.createTokensWithoutTargetText(realPage, targetWords, false, true); + List tokens = + redactController.createTokensWithoutTargetText( + realDocument, realPage, targetWords, false, true); String reconstructedText = extractTextFromTokens(tokens); assertTrue(reconstructedText.contains("testing"), "Partial matches should remain"); @@ -856,11 +917,14 @@ class RedactControllerTest { Set targetWords = Set.of("sensitive"); - List tokens = redactController.createTokensWithoutTargetText(realPage, targetWords, false, false); + List tokens = + redactController.createTokensWithoutTargetText( + realDocument, realPage, targetWords, false, false); String reconstructedText = extractTextFromTokens(tokens); - assertFalse(reconstructedText.contains("sensitive"), - "Text should be redacted regardless of operator type"); + assertFalse( + reconstructedText.contains("sensitive"), + "Text should be redacted regardless of operator type"); } @Test @@ -884,7 +948,10 @@ class RedactControllerTest { void shouldHandleEmptyTokenList() throws Exception { List emptyTokens = Collections.emptyList(); - assertDoesNotThrow(() -> redactController.writeFilteredContentStream(realDocument, realPage, emptyTokens)); + assertDoesNotThrow( + () -> + redactController.writeFilteredContentStream( + realDocument, realPage, emptyTokens)); assertNotNull(realPage.getContents(), "Page should still have content stream"); } @@ -906,20 +973,27 @@ class RedactControllerTest { @DisplayName("Placeholder creation should maintain text width") void shouldCreateWidthMatchingPlaceholder() throws Exception { String originalText = "confidential"; - String placeholder = redactController.createPlaceholder(originalText); + String placeholder = + redactController.createPlaceholderWithFont( + originalText, new PDType1Font(Standard14Fonts.FontName.HELVETICA)); - assertEquals(originalText.length(), placeholder.length(), - "Placeholder should maintain character count for width preservation"); + assertEquals( + originalText.length(), + placeholder.length(), + "Placeholder should maintain character count for width preservation"); } @Test @DisplayName("Placeholder should handle special characters") void shouldHandleSpecialCharactersInPlaceholder() throws Exception { String originalText = "café naïve"; - String placeholder = redactController.createPlaceholder(originalText); + String placeholder = + redactController.createPlaceholderWithFont( + originalText, new PDType1Font(Standard14Fonts.FontName.HELVETICA)); assertEquals(originalText.length(), placeholder.length()); - assertFalse(placeholder.contains("café"), "Placeholder should not contain original text"); + assertFalse( + placeholder.contains("café"), "Placeholder should not contain original text"); } @Test @@ -929,7 +1003,9 @@ class RedactControllerTest { Set targetWords = Set.of("secret"); - List filteredTokens = redactController.createTokensWithoutTargetText(realPage, targetWords, false, false); + List filteredTokens = + redactController.createTokensWithoutTargetText( + realDocument, realPage, targetWords, false, false); redactController.writeFilteredContentStream(realDocument, realPage, filteredTokens); assertNotNull(realPage.getContents()); @@ -946,15 +1022,21 @@ class RedactControllerTest { Set targetWords = Set.of("confidential"); - List filteredTokens = redactController.createTokensWithoutTargetText(realPage, targetWords, false, false); + List filteredTokens = + redactController.createTokensWithoutTargetText( + realDocument, realPage, targetWords, false, false); - long filteredPositioning = filteredTokens.stream() - .filter(token -> token instanceof Operator op && - (op.getName().equals("Td") || op.getName().equals("TD") || op.getName().equals("Tm"))) - .count(); + long filteredPositioning = + filteredTokens.stream() + .filter( + token -> + token instanceof Operator op + && ("Td".equals(op.getName()) + || "TD".equals(op.getName()) + || "Tm".equals(op.getName()))) + .count(); - assertTrue(filteredPositioning > 0, - "Positioning operators should be preserved"); + assertTrue(filteredPositioning > 0, "Positioning operators should be preserved"); } @Test @@ -966,16 +1048,21 @@ class RedactControllerTest { } realDocument.addPage(realPage); realPage.setResources(new PDResources()); - realPage.getResources().put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA)); + realPage.getResources() + .put( + COSName.getPDFName("F1"), + new PDType1Font(Standard14Fonts.FontName.HELVETICA)); - try (PDPageContentStream contentStream = new PDPageContentStream(realDocument, realPage)) { + try (PDPageContentStream contentStream = + new PDPageContentStream(realDocument, realPage)) { contentStream.setLineWidth(2); contentStream.moveTo(100, 100); contentStream.lineTo(200, 200); contentStream.stroke(); contentStream.beginText(); - contentStream.setFont(realPage.getResources().getFont(COSName.getPDFName("F1")), 12); + contentStream.setFont( + realPage.getResources().getFont(COSName.getPDFName("F1")), 12); contentStream.newLineAtOffset(50, 750); contentStream.showText("This is a complex document with "); contentStream.setTextRise(5); @@ -990,19 +1077,27 @@ class RedactControllerTest { Set targetWords = Set.of("confidential"); - List tokens = redactController.createTokensWithoutTargetText(realPage, targetWords, false, false); + List tokens = + redactController.createTokensWithoutTargetText( + realDocument, realPage, targetWords, false, false); assertNotNull(tokens); assertFalse(tokens.isEmpty()); String reconstructedText = extractTextFromTokens(tokens); - assertFalse(reconstructedText.contains("confidential"), "Target text should be redacted"); + assertFalse( + reconstructedText.contains("confidential"), "Target text should be redacted"); - boolean hasGraphicsOperators = tokens.stream() - .anyMatch(token -> token instanceof Operator op && - (op.getName().equals("re") || op.getName().equals("f") || - op.getName().equals("m") || op.getName().equals("l") || - op.getName().equals("S"))); + boolean hasGraphicsOperators = + tokens.stream() + .anyMatch( + token -> + token instanceof Operator op + && ("re".equals(op.getName()) + || "f".equals(op.getName()) + || "m".equals(op.getName()) + || "l".equals(op.getName()) + || "S".equals(op.getName()))); assertTrue(hasGraphicsOperators, "Graphics operators should be preserved"); } @@ -1019,10 +1114,12 @@ class RedactControllerTest { // Create resources PDResources resources = new PDResources(); - resources.put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA)); + resources.put( + COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA)); realPage.setResources(resources); - try (PDPageContentStream contentStream = new PDPageContentStream(realDocument, realPage)) { + try (PDPageContentStream contentStream = + new PDPageContentStream(realDocument, realPage)) { contentStream.beginText(); contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12); contentStream.newLineAtOffset(50, 750); @@ -1180,7 +1277,8 @@ class RedactControllerTest { } realDocument.addPage(realPage); realPage.setResources(new PDResources()); - realPage.getResources().put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA)); + realPage.getResources() + .put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA)); try (PDPageContentStream contentStream = new PDPageContentStream(realDocument, realPage)) { contentStream.beginText(); @@ -1198,7 +1296,8 @@ class RedactControllerTest { } realDocument.addPage(realPage); realPage.setResources(new PDResources()); - realPage.getResources().put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA)); + realPage.getResources() + .put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA)); try (PDPageContentStream contentStream = new PDPageContentStream(realDocument, realPage)) { contentStream.beginText(); @@ -1221,7 +1320,8 @@ class RedactControllerTest { } realDocument.addPage(realPage); realPage.setResources(new PDResources()); - realPage.getResources().put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA)); + realPage.getResources() + .put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA)); try (PDPageContentStream contentStream = new PDPageContentStream(realDocument, realPage)) { contentStream.setLineWidth(2); @@ -1248,7 +1348,8 @@ class RedactControllerTest { } realDocument.addPage(realPage); realPage.setResources(new PDResources()); - realPage.getResources().put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA)); + realPage.getResources() + .put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA)); try (PDPageContentStream contentStream = new PDPageContentStream(realDocument, realPage)) { contentStream.beginText(); @@ -1266,28 +1367,29 @@ class RedactControllerTest { // Helper for token creation private List createSampleTokenList() { return List.of( - Operator.getOperator("BT"), - COSName.getPDFName("F1"), - new COSFloat(12), - Operator.getOperator("Tf"), - new COSString("Sample text"), - Operator.getOperator("Tj"), - Operator.getOperator("ET") - ); + Operator.getOperator("BT"), + COSName.getPDFName("F1"), + new COSFloat(12), + Operator.getOperator("Tf"), + new COSString("Sample text"), + Operator.getOperator("Tj"), + Operator.getOperator("ET")); } private List getOriginalTokens() throws Exception { // Create a new page to avoid side effects from other tests PDPage pageForTokenExtraction = new PDPage(PDRectangle.A4); pageForTokenExtraction.setResources(realPage.getResources()); - try (PDPageContentStream contentStream = new PDPageContentStream(realDocument, pageForTokenExtraction)) { - contentStream.beginText(); - contentStream.setFont(realPage.getResources().getFont(COSName.getPDFName("F1")), 12); - contentStream.newLineAtOffset(50, 750); - contentStream.showText("Original content"); - contentStream.endText(); + try (PDPageContentStream contentStream = + new PDPageContentStream(realDocument, pageForTokenExtraction)) { + contentStream.beginText(); + contentStream.setFont(realPage.getResources().getFont(COSName.getPDFName("F1")), 12); + contentStream.newLineAtOffset(50, 750); + contentStream.showText("Original content"); + contentStream.endText(); } - return redactController.createTokensWithoutTargetText(pageForTokenExtraction, Collections.emptySet(), false, false); + return redactController.createTokensWithoutTargetText( + realDocument, pageForTokenExtraction, Collections.emptySet(), false, false); } private String extractTextFromTokens(List tokens) { diff --git a/stirling-pdf/src/test/java/stirling/software/SPDF/pdf/TextFinderTest.java b/app/core/src/test/java/stirling/software/SPDF/pdf/TextFinderTest.java similarity index 69% rename from stirling-pdf/src/test/java/stirling/software/SPDF/pdf/TextFinderTest.java rename to app/core/src/test/java/stirling/software/SPDF/pdf/TextFinderTest.java index ebb5bebf7..3e5092070 100644 --- a/stirling-pdf/src/test/java/stirling/software/SPDF/pdf/TextFinderTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/pdf/TextFinderTest.java @@ -1,5 +1,11 @@ package stirling.software.SPDF.pdf; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.io.IOException; import java.util.List; @@ -10,11 +16,6 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.apache.pdfbox.pdmodel.font.Standard14Fonts; import org.junit.jupiter.api.AfterEach; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -34,33 +35,44 @@ class TextFinderTest { private PDPage page; // Helpers - private void testTextFinding(String pageContent, String searchTerm, boolean useRegex, boolean wholeWord, - String[] expectedTexts, int expectedCount) throws IOException { + private void testTextFinding( + String pageContent, + String searchTerm, + boolean useRegex, + boolean wholeWord, + String[] expectedTexts, + int expectedCount) + throws IOException { addTextToPage(pageContent); TextFinder textFinder = new TextFinder(searchTerm, useRegex, wholeWord); textFinder.getText(document); List foundTexts = textFinder.getFoundTexts(); - assertEquals(expectedCount, foundTexts.size(), - String.format("Expected %d matches for search term '%s'", expectedCount, searchTerm)); + assertEquals( + expectedCount, + foundTexts.size(), + String.format( + "Expected %d matches for search term '%s'", expectedCount, searchTerm)); if (expectedTexts != null) { for (String expectedText : expectedTexts) { - assertTrue(foundTexts.stream().anyMatch(text -> text.getText().equals(expectedText)), - String.format("Expected to find text: '%s'", expectedText)); + assertTrue( + foundTexts.stream().anyMatch(text -> text.getText().equals(expectedText)), + String.format("Expected to find text: '%s'", expectedText)); } } // Verify basic properties of found texts - foundTexts.forEach(text -> { - assertNotNull(text.getText()); - assertTrue(text.getX1() >= 0); - assertTrue(text.getY1() >= 0); - assertTrue(text.getX2() >= text.getX1()); - assertTrue(text.getY2() >= text.getY1()); - assertEquals(0, text.getPageIndex()); // Single page test - }); + foundTexts.forEach( + text -> { + assertNotNull(text.getText()); + assertTrue(text.getX1() >= 0); + assertTrue(text.getY1() >= 0); + assertTrue(text.getX2() >= text.getX1()); + assertTrue(text.getY2() >= text.getY1()); + assertEquals(0, text.getPageIndex()); // Single page test + }); } @BeforeEach @@ -84,25 +96,37 @@ class TextFinderTest { @Test @DisplayName("Should find simple text correctly") void findSimpleText() throws IOException { - testTextFinding("This is a confidential document with secret information.", - "confidential", false, false, - new String[]{"confidential"}, 1); + testTextFinding( + "This is a confidential document with secret information.", + "confidential", + false, + false, + new String[] {"confidential"}, + 1); } @Test @DisplayName("Should perform case-insensitive search") void performCaseInsensitiveSearch() throws IOException { - testTextFinding("This document contains CONFIDENTIAL information.", - "confidential", false, false, - new String[]{"CONFIDENTIAL"}, 1); + testTextFinding( + "This document contains CONFIDENTIAL information.", + "confidential", + false, + false, + new String[] {"CONFIDENTIAL"}, + 1); } @Test @DisplayName("Should find multiple occurrences of same term") void findMultipleOccurrences() throws IOException { - testTextFinding("The secret code is secret123. Keep this secret safe!", - "secret", false, false, - new String[]{"secret", "secret", "secret"}, 3); + testTextFinding( + "The secret code is secret123. Keep this secret safe!", + "secret", + false, + false, + new String[] {"secret", "secret", "secret"}, + 3); } @Test @@ -131,33 +155,49 @@ class TextFinderTest { @Test @DisplayName("Should find only whole words when enabled") void findOnlyWholeWords() throws IOException { - testTextFinding("This is a test testing document with tested results.", - "test", false, true, - new String[]{"test"}, 1); + testTextFinding( + "This is a test testing document with tested results.", + "test", + false, + true, + new String[] {"test"}, + 1); } @Test @DisplayName("Should find partial matches when whole word search disabled") void findPartialMatches() throws IOException { - testTextFinding("This is a test testing document with tested results.", - "test", false, false, - new String[]{"test", "test", "test"}, 3); + testTextFinding( + "This is a test testing document with tested results.", + "test", + false, + false, + new String[] {"test", "test", "test"}, + 3); } @Test @DisplayName("Should handle punctuation boundaries correctly") void handlePunctuationBoundaries() throws IOException { - testTextFinding("Hello, world! Testing: test-case (test).", - "test", false, true, - new String[]{"test"}, 2); // Both standalone "test" and "test" in "test-case" + testTextFinding( + "Hello, world! Testing: test-case (test).", + "test", + false, + true, + new String[] {"test"}, + 2); // Both standalone "test" and "test" in "test-case" } @Test @DisplayName("Should handle word boundaries with special characters") void handleSpecialCharacterBoundaries() throws IOException { - testTextFinding("Email: test@example.com and test.txt file", - "test", false, true, - new String[]{"test"}, 2); // Both in email and filename should match + testTextFinding( + "Email: test@example.com and test.txt file", + "test", + false, + true, + new String[] {"test"}, + 2); // Both in email and filename should match } } @@ -168,46 +208,64 @@ class TextFinderTest { @Test @DisplayName("Should find text matching regex pattern") void findTextMatchingRegex() throws IOException { - testTextFinding("Contact John at 123-45-6789 or Jane at 987-65-4321 for details.", - "\\d{3}-\\d{2}-\\d{4}", true, false, - new String[]{"123-45-6789", "987-65-4321"}, 2); + testTextFinding( + "Contact John at 123-45-6789 or Jane at 987-65-4321 for details.", + "\\d{3}-\\d{2}-\\d{4}", + true, + false, + new String[] {"123-45-6789", "987-65-4321"}, + 2); } @Test @DisplayName("Should find email addresses with regex") void findEmailAddresses() throws IOException { - testTextFinding("Email: test@example.com and admin@test.org", - "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", true, false, - new String[]{"test@example.com", "admin@test.org"}, 2); + testTextFinding( + "Email: test@example.com and admin@test.org", + "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", + true, + false, + new String[] {"test@example.com", "admin@test.org"}, + 2); } @Test @DisplayName("Should combine regex with whole word search") void combineRegexWithWholeWord() throws IOException { - testTextFinding("Email: test@example.com and admin@test.org", - "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", true, true, - new String[]{"test@example.com", "admin@test.org"}, 2); + testTextFinding( + "Email: test@example.com and admin@test.org", + "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", + true, + true, + new String[] {"test@example.com", "admin@test.org"}, + 2); } @Test @DisplayName("Should find currency patterns") void findCurrencyPatterns() throws IOException { - testTextFinding("Price: $100.50 and €75.25", - "\\$\\d+\\.\\d{2}", true, false, - new String[]{"$100.50"}, 1); + testTextFinding( + "Price: $100.50 and €75.25", + "\\$\\d+\\.\\d{2}", + true, + false, + new String[] {"$100.50"}, + 1); } @ParameterizedTest - @ValueSource(strings = { - "\\d{4}-\\d{2}-\\d{2}", // Date pattern - "\\b[A-Z]{2,}\\b", // Uppercase words - "\\w+@\\w+\\.\\w+", // Simple email pattern - "\\$\\d+", // Simple currency - "\\b\\d{3,4}\\b" // 3-4 digit numbers - }) + @ValueSource( + strings = { + "\\d{4}-\\d{2}-\\d{2}", // Date pattern + "\\b[A-Z]{2,}\\b", // Uppercase words + "\\w+@\\w+\\.\\w+", // Simple email pattern + "\\$\\d+", // Simple currency + "\\b\\d{3,4}\\b" // 3-4 digit numbers + }) @DisplayName("Should handle various regex patterns") void handleVariousRegexPatterns(String regexPattern) throws IOException { - String testContent = "Date: 2023-12-25, Email: test@domain.com, Price: $250, Code: ABC123, Number: 1234"; + String testContent = + "Date: 2023-12-25, Email: test@domain.com, Price: $250, Code: ABC123, Number: 1234"; addTextToPage(testContent); TextFinder textFinder = new TextFinder(regexPattern, true, false); @@ -215,7 +273,9 @@ class TextFinderTest { List foundTexts = textFinder.getFoundTexts(); // Each pattern should find at least one match in our test content - assertFalse(foundTexts.isEmpty(), String.format("Pattern '%s' should find at least one match", regexPattern)); + assertFalse( + foundTexts.isEmpty(), + String.format("Pattern '%s' should find at least one match", regexPattern)); } @Test @@ -230,9 +290,10 @@ class TextFinderTest { assertNotNull(foundTexts); } catch (java.util.regex.PatternSyntaxException e) { assertNotNull(e.getMessage()); - assertTrue(e.getMessage().contains("Unclosed character class") || - e.getMessage().contains("syntax"), - "Exception should indicate regex syntax error"); + assertTrue( + e.getMessage().contains("Unclosed character class") + || e.getMessage().contains("syntax"), + "Exception should indicate regex syntax error"); } catch (RuntimeException | IOException e) { assertNotNull(e.getMessage()); } @@ -246,33 +307,38 @@ class TextFinderTest { @Test @DisplayName("Should handle international characters") void handleInternationalCharacters() throws IOException { - testTextFinding("Hello café naïve résumé", - "café", false, false, - new String[]{"café"}, 1); + testTextFinding( + "Hello café naïve résumé", "café", false, false, new String[] {"café"}, 1); } @Test @DisplayName("Should find text with accented characters") void findAccentedCharacters() throws IOException { - testTextFinding("Café, naïve, résumé, piñata", - "café", false, false, - new String[]{"Café"}, 1); // Case insensitive + testTextFinding( + "Café, naïve, résumé, piñata", + "café", + false, + false, + new String[] {"Café"}, + 1); // Case insensitive } @Test @DisplayName("Should handle special symbols") void handleSpecialSymbols() throws IOException { - testTextFinding("Symbols: © ® ™ ± × ÷ § ¶", - "©", false, false, - new String[]{"©"}, 1); + testTextFinding("Symbols: © ® ™ ± × ÷ § ¶", "©", false, false, new String[] {"©"}, 1); } @Test @DisplayName("Should find currency symbols") void findCurrencySymbols() throws IOException { - testTextFinding("Prices: $100 €75 £50 ¥1000", - "[€£¥]", true, false, - new String[]{"€", "£", "¥"}, 3); + testTextFinding( + "Prices: $100 €75 £50 ¥1000", + "[€£¥]", + true, + false, + new String[] {"€", "£", "¥"}, + 3); } } @@ -330,7 +396,7 @@ class TextFinderTest { String longTerm = "a".repeat(1000); String content = "Short text with " + longTerm + " embedded."; - testTextFinding(content, longTerm, false, false, new String[]{longTerm}, 1); + testTextFinding(content, longTerm, false, false, new String[] {longTerm}, 1); } @Test @@ -350,8 +416,9 @@ class TextFinderTest { long endTime = System.currentTimeMillis(); assertEquals(10, foundTexts.size()); - assertTrue(endTime - startTime < 3000, - "Multi-page search should complete within 3 seconds"); + assertTrue( + endTime - startTime < 3000, + "Multi-page search should complete within 3 seconds"); } } @@ -402,12 +469,13 @@ class TextFinderTest { String complexRegex = "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z\\d]{6}"; - assertDoesNotThrow(() -> { - TextFinder textFinder = new TextFinder(complexRegex, true, false); - textFinder.getText(document); - List foundTexts = textFinder.getFoundTexts(); - assertNotNull(foundTexts); - }); + assertDoesNotThrow( + () -> { + TextFinder textFinder = new TextFinder(complexRegex, true, false); + textFinder.getText(document); + List foundTexts = textFinder.getFoundTexts(); + assertNotNull(foundTexts); + }); } @ParameterizedTest @@ -464,10 +532,11 @@ class TextFinderTest { List foundTexts = textFinder.getFoundTexts(); assertFalse(foundTexts.isEmpty()); - foundTexts.forEach(text -> { - assertNotNull(text.getText()); - assertTrue(text.getX1() >= 0 && text.getY1() >= 0); - }); + foundTexts.forEach( + text -> { + assertNotNull(text.getText()); + assertTrue(text.getX1() >= 0 && text.getY1() >= 0); + }); } } @@ -485,8 +554,10 @@ class TextFinderTest { textFinder.getText(document); List foundTexts = textFinder.getFoundTexts(); - assertEquals(1, foundTexts.size(), - "Should find exactly one standalone '1', not the ones embedded in other numbers/codes"); + assertEquals( + 1, + foundTexts.size(), + "Should find exactly one standalone '1', not the ones embedded in other numbers/codes"); assertEquals("1", foundTexts.get(0).getText()); } @@ -500,14 +571,16 @@ class TextFinderTest { textFinder.getText(document); List foundTexts = textFinder.getFoundTexts(); - assertTrue(foundTexts.size() >= 3, - "Should find multiple instances of '1' including standalone, in '1234', and in 'A1B'"); + assertTrue( + foundTexts.size() >= 3, + "Should find multiple instances of '1' including standalone, in '1234', and in 'A1B'"); } @Test @DisplayName("Should find single characters in various contexts") void findSingleCharacters() throws IOException { - String content = "Grade: A. Section B has item A-1. The letter A appears multiple times."; + String content = + "Grade: A. Section B has item A-1. The letter A appears multiple times."; addTextToPage(content); TextFinder textFinder = new TextFinder("A", false, true); @@ -522,24 +595,29 @@ class TextFinderTest { } @Test - @DisplayName("Should handle digits at word boundaries correctly") + @DisplayName("Digits as strict standalone tokens (exclude decimals and suffixes)") void findDigitsAtWordBoundaries() throws IOException { - String content = "Numbers: 1, 2, 3. Code: 123. Version: 1.0. Item1 and Item2."; + String content = + "Numbers: 1, 2, 3. Code: 123. Version: 1.0. Item1 and Item2. Price: 2,50€"; addTextToPage(content); TextFinder textFinder1 = new TextFinder("1", false, true); textFinder1.getText(document); List foundTexts1 = textFinder1.getFoundTexts(); - assertEquals(1, foundTexts1.size(), - "Should find only the standalone '1' at the beginning"); + assertEquals( + 1, + foundTexts1.size(), + "Should find only the standalone '1'; do not count the '1' in '1.0' or in 'Item1'."); TextFinder textFinder2 = new TextFinder("2", false, true); textFinder2.getText(document); List foundTexts2 = textFinder2.getFoundTexts(); - assertEquals(1, foundTexts2.size(), - "Should find only the standalone '2' in the number list"); + assertEquals( + 1, + foundTexts2.size(), + "Should find only the standalone '2' in the number list"); } @Test @@ -566,8 +644,10 @@ class TextFinderTest { textFinder.getText(document); List foundTexts = textFinder.getFoundTexts(); - assertEquals(2, foundTexts.size(), - "Should find both '1' instances despite spacing variations"); + assertEquals( + 2, + foundTexts.size(), + "Should find both '1' instances despite spacing variations"); } } From 5fb207492e340bf5b94cdd16e1335a70b97002e2 Mon Sep 17 00:00:00 2001 From: "stirlingbot[bot]" <195170888+stirlingbot[bot]@users.noreply.github.com> Date: Sun, 24 Aug 2025 21:26:01 +0100 Subject: [PATCH 09/20] Update 3rd Party Licenses (#4247) Auto-generated by stirlingbot[bot] Signed-off-by: stirlingbot[bot] Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com> --- .../resources/static/3rdPartyLicenses.json | 191 +++++++++--------- 1 file changed, 96 insertions(+), 95 deletions(-) diff --git a/app/core/src/main/resources/static/3rdPartyLicenses.json b/app/core/src/main/resources/static/3rdPartyLicenses.json index 062818603..4038f11b8 100644 --- a/app/core/src/main/resources/static/3rdPartyLicenses.json +++ b/app/core/src/main/resources/static/3rdPartyLicenses.json @@ -504,7 +504,7 @@ { "moduleName": "com.zaxxer:HikariCP", "moduleUrl": "https://github.com/brettwooldridge/HikariCP", - "moduleVersion": "6.3.1", + "moduleVersion": "6.3.2", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" }, @@ -559,56 +559,56 @@ { "moduleName": "io.jsonwebtoken:jjwt-api", "moduleUrl": "https://github.com/jwtk/jjwt", - "moduleVersion": "0.12.6", - "moduleLicense": "Apache License, Version 2.0", + "moduleVersion": "0.12.7", + "moduleLicense": "Apache-2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "io.jsonwebtoken:jjwt-impl", "moduleUrl": "https://github.com/jwtk/jjwt", - "moduleVersion": "0.12.6", - "moduleLicense": "Apache License, Version 2.0", + "moduleVersion": "0.12.7", + "moduleLicense": "Apache-2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "io.jsonwebtoken:jjwt-jackson", "moduleUrl": "https://github.com/jwtk/jjwt", - "moduleVersion": "0.12.6", - "moduleLicense": "Apache License, Version 2.0", + "moduleVersion": "0.12.7", + "moduleLicense": "Apache-2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "io.micrometer:micrometer-commons", "moduleUrl": "https://github.com/micrometer-metrics/micrometer", - "moduleVersion": "1.15.2", + "moduleVersion": "1.15.3", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, { "moduleName": "io.micrometer:micrometer-core", "moduleUrl": "https://github.com/micrometer-metrics/micrometer", - "moduleVersion": "1.15.2", + "moduleVersion": "1.15.3", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, { "moduleName": "io.micrometer:micrometer-jakarta9", "moduleUrl": "https://github.com/micrometer-metrics/micrometer", - "moduleVersion": "1.15.2", + "moduleVersion": "1.15.3", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, { "moduleName": "io.micrometer:micrometer-observation", "moduleUrl": "https://github.com/micrometer-metrics/micrometer", - "moduleVersion": "1.15.2", + "moduleVersion": "1.15.3", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, { "moduleName": "io.micrometer:micrometer-registry-prometheus", "moduleUrl": "https://github.com/micrometer-metrics/micrometer", - "moduleVersion": "1.15.2", + "moduleVersion": "1.15.3", "moduleLicense": "The Apache Software License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, @@ -657,21 +657,21 @@ { "moduleName": "io.swagger.core.v3:swagger-annotations-jakarta", "moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-annotations", - "moduleVersion": "2.2.35", + "moduleVersion": "2.2.36", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "io.swagger.core.v3:swagger-core-jakarta", "moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-core", - "moduleVersion": "2.2.35", + "moduleVersion": "2.2.36", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "io.swagger.core.v3:swagger-models-jakarta", "moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-models", - "moduleVersion": "2.2.35", + "moduleVersion": "2.2.36", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, @@ -803,7 +803,7 @@ }, { "moduleName": "net.bytebuddy:byte-buddy", - "moduleVersion": "1.17.6", + "moduleVersion": "1.17.7", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" }, @@ -946,7 +946,7 @@ { "moduleName": "org.apache.tomcat.embed:tomcat-embed-el", "moduleUrl": "https://tomcat.apache.org/", - "moduleVersion": "10.1.43", + "moduleVersion": "10.1.44", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" }, @@ -1041,196 +1041,196 @@ { "moduleName": "org.eclipse.angus:angus-mail", "moduleUrl": "https://www.eclipse.org", - "moduleVersion": "2.0.3", + "moduleVersion": "2.0.4", "moduleLicense": "GPL2 w/ CPE", "moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html" }, { "moduleName": "org.eclipse.angus:jakarta.mail", "moduleUrl": "https://www.eclipse.org", - "moduleVersion": "2.0.3", + "moduleVersion": "2.0.4", "moduleLicense": "GPL2 w/ CPE", "moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html" }, { "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-client", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-common", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-servlet", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-annotations", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-plus", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlet", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlets", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.ee10:jetty-ee10-webapp", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-client", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-common", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-server", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-api", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-common", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-alpn-client", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-client", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-ee", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-http", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-io", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-plus", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-security", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-server", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-session", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-util", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, { "moduleName": "org.eclipse.jetty:jetty-xml", "moduleUrl": "https://jetty.org/", - "moduleVersion": "12.0.23", + "moduleVersion": "12.0.25", "moduleLicense": "Eclipse Public License - Version 2.0", "moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/" }, @@ -1272,15 +1272,16 @@ { "moduleName": "org.hibernate.orm:hibernate-core", "moduleUrl": "https://www.hibernate.org/orm/6.6", - "moduleVersion": "6.6.22.Final", + "moduleVersion": "6.6.26.Final", "moduleLicense": "GNU Library General Public License v2.1 or later", "moduleLicenseUrl": "https://www.opensource.org/licenses/LGPL-2.1" }, { "moduleName": "org.hibernate.validator:hibernate-validator", - "moduleVersion": "8.0.2.Final", + "moduleUrl": "https://hibernate.org/validator", + "moduleVersion": "8.0.3.Final", "moduleLicense": "Apache License 2.0", - "moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt" + "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" }, { "moduleName": "org.jboss.logging:jboss-logging", @@ -1470,320 +1471,320 @@ }, { "moduleName": "org.springdoc:springdoc-openapi-starter-common", - "moduleVersion": "2.8.9", + "moduleVersion": "2.8.11", "moduleLicense": "The Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" }, { "moduleName": "org.springdoc:springdoc-openapi-starter-webmvc-api", - "moduleVersion": "2.8.9", + "moduleVersion": "2.8.11", "moduleLicense": "The Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" }, { "moduleName": "org.springdoc:springdoc-openapi-starter-webmvc-ui", - "moduleVersion": "2.8.9", + "moduleVersion": "2.8.11", "moduleLicense": "The Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" }, { "moduleName": "org.springframework.boot:spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-actuator", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-actuator-autoconfigure", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-autoconfigure", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-actuator", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-aop", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-cache", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-data-jpa", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-jdbc", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-jetty", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-json", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-logging", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-mail", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-oauth2-client", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-security", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-thymeleaf", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-validation", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.boot:spring-boot-starter-web", "moduleUrl": "https://spring.io/projects/spring-boot", - "moduleVersion": "3.5.4", + "moduleVersion": "3.5.5", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.data:spring-data-commons", "moduleUrl": "https://spring.io/projects/spring-data", - "moduleVersion": "3.5.2", + "moduleVersion": "3.5.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.data:spring-data-jpa", "moduleUrl": "https://projects.spring.io/spring-data-jpa", - "moduleVersion": "3.5.2", + "moduleVersion": "3.5.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.security:spring-security-config", "moduleUrl": "https://spring.io/projects/spring-security", - "moduleVersion": "6.5.2", + "moduleVersion": "6.5.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.security:spring-security-core", "moduleUrl": "https://spring.io/projects/spring-security", - "moduleVersion": "6.5.2", + "moduleVersion": "6.5.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.security:spring-security-crypto", "moduleUrl": "https://spring.io/projects/spring-security", - "moduleVersion": "6.5.2", + "moduleVersion": "6.5.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.security:spring-security-oauth2-client", "moduleUrl": "https://spring.io/projects/spring-security", - "moduleVersion": "6.5.2", + "moduleVersion": "6.5.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.security:spring-security-oauth2-core", "moduleUrl": "https://spring.io/projects/spring-security", - "moduleVersion": "6.5.2", + "moduleVersion": "6.5.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.security:spring-security-oauth2-jose", "moduleUrl": "https://spring.io/projects/spring-security", - "moduleVersion": "6.5.2", + "moduleVersion": "6.5.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.security:spring-security-saml2-service-provider", "moduleUrl": "https://spring.io/projects/spring-security", - "moduleVersion": "6.5.2", + "moduleVersion": "6.5.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.security:spring-security-web", "moduleUrl": "https://spring.io/projects/spring-security", - "moduleVersion": "6.5.2", + "moduleVersion": "6.5.3", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework.session:spring-session-core", "moduleUrl": "https://spring.io/projects/spring-session", - "moduleVersion": "3.5.1", + "moduleVersion": "3.5.2", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-aop", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.2.9", + "moduleVersion": "6.2.10", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-aspects", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.2.9", + "moduleVersion": "6.2.10", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-beans", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.2.9", + "moduleVersion": "6.2.10", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-context", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.2.9", + "moduleVersion": "6.2.10", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-context-support", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.2.9", + "moduleVersion": "6.2.10", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-core", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.2.9", + "moduleVersion": "6.2.10", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-expression", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.2.9", + "moduleVersion": "6.2.10", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-jcl", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.2.9", + "moduleVersion": "6.2.10", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-jdbc", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.2.9", + "moduleVersion": "6.2.10", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-orm", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.2.9", + "moduleVersion": "6.2.10", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-tx", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.2.9", + "moduleVersion": "6.2.10", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-web", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.2.9", + "moduleVersion": "6.2.10", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, { "moduleName": "org.springframework:spring-webmvc", "moduleUrl": "https://github.com/spring-projects/spring-framework", - "moduleVersion": "6.2.9", + "moduleVersion": "6.2.10", "moduleLicense": "Apache License, Version 2.0", "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" }, @@ -1821,7 +1822,7 @@ { "moduleName": "org.webjars:swagger-ui", "moduleUrl": "https://www.webjars.org", - "moduleVersion": "5.21.0", + "moduleVersion": "5.27.1", "moduleLicense": "Apache-2.0" }, { From 4cd1de410148676ee1b26a52000b465342f6c144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20T=C3=BCrkay?= <142736624+turkaysoftware@users.noreply.github.com> Date: Sun, 24 Aug 2025 23:31:40 +0300 Subject: [PATCH 10/20] Update messages_tr_TR.properties (#4274) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translated 100% specifically into Turkish. No machine translation or artificial intelligence was used. All files have been correctly translated into Turkish together with the file providers. Best regards. - Türkay Software https://www.turkaysoftware.com # Description of Changes --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [x] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [x] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [x] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --- .../main/resources/messages_tr_TR.properties | 646 +++++++++--------- 1 file changed, 323 insertions(+), 323 deletions(-) diff --git a/app/core/src/main/resources/messages_tr_TR.properties b/app/core/src/main/resources/messages_tr_TR.properties index 155b4365d..ca084cc0d 100644 --- a/app/core/src/main/resources/messages_tr_TR.properties +++ b/app/core/src/main/resources/messages_tr_TR.properties @@ -1,138 +1,138 @@ ########### # Generic # ########### -# the direction that the language is written (ltr = left to right, rtl = right to left) +# the direction that the language is written (ltr=left to right, rtl=right to left) language.direction=ltr # Language names for reuse throughout the application -lang.afr=Afrikaans -lang.amh=Amharic -lang.ara=Arabic -lang.asm=Assamese -lang.aze=Azerbaijani -lang.aze_cyrl=Azerbaijani (Cyrillic) -lang.bel=Belarusian -lang.ben=Bengali -lang.bod=Tibetan -lang.bos=Bosnian -lang.bre=Breton -lang.bul=Bulgarian -lang.cat=Catalan -lang.ceb=Cebuano -lang.ces=Czech -lang.chi_sim=Chinese (Simplified) -lang.chi_sim_vert=Chinese (Simplified, Vertical) -lang.chi_tra=Chinese (Traditional) -lang.chi_tra_vert=Chinese (Traditional, Vertical) -lang.chr=Cherokee -lang.cos=Corsican -lang.cym=Welsh -lang.dan=Danish -lang.dan_frak=Danish (Fraktur) -lang.deu=German -lang.deu_frak=German (Fraktur) -lang.div=Divehi -lang.dzo=Dzongkha -lang.ell=Greek -lang.eng=English -lang.enm=English, Middle (1100-1500) -lang.epo=Esperanto -lang.equ=Math / equation detection module -lang.est=Estonian -lang.eus=Basque -lang.fao=Faroese -lang.fas=Persian -lang.fil=Filipino -lang.fin=Finnish -lang.fra=French -lang.frk=Frankish -lang.frm=French, Middle (ca.1400-1600) -lang.fry=Western Frisian -lang.gla=Scottish Gaelic -lang.gle=Irish -lang.glg=Galician -lang.grc=Ancient Greek -lang.guj=Gujarati -lang.hat=Haitian, Haitian Creole -lang.heb=Hebrew -lang.hin=Hindi -lang.hrv=Croatian -lang.hun=Hungarian -lang.hye=Armenian -lang.iku=Inuktitut -lang.ind=Indonesian -lang.isl=Icelandic -lang.ita=Italian -lang.ita_old=Italian (Old) -lang.jav=Javanese -lang.jpn=Japanese -lang.jpn_vert=Japanese (Vertical) -lang.kan=Kannada -lang.kat=Georgian -lang.kat_old=Georgian (Old) -lang.kaz=Kazakh -lang.khm=Central Khmer -lang.kir=Kirghiz, Kyrgyz -lang.kmr=Northern Kurdish -lang.kor=Korean -lang.kor_vert=Korean (Vertical) -lang.lao=Lao -lang.lat=Latin -lang.lav=Latvian -lang.lit=Lithuanian -lang.ltz=Luxembourgish -lang.mal=Malayalam -lang.mar=Marathi -lang.mkd=Macedonian -lang.mlt=Maltese -lang.mon=Mongolian -lang.mri=Maori -lang.msa=Malay -lang.mya=Burmese -lang.nep=Nepali -lang.nld=Dutch; Flemish -lang.nor=Norwegian -lang.oci=Occitan (post 1500) -lang.ori=Oriya -lang.osd=Orientation and script detection module -lang.pan=Panjabi, Punjabi -lang.pol=Polish -lang.por=Portuguese -lang.pus=Pushto, Pashto -lang.que=Quechua -lang.ron=Romanian, Moldavian, Moldovan -lang.rus=Russian -lang.san=Sanskrit -lang.sin=Sinhala, Sinhalese -lang.slk=Slovak -lang.slk_frak=Slovak (Fraktur) -lang.slv=Slovenian -lang.snd=Sindhi -lang.spa=Spanish -lang.spa_old=Spanish (Old) -lang.sqi=Albanian -lang.srp=Serbian -lang.srp_latn=Serbian (Latin) -lang.sun=Sundanese -lang.swa=Swahili -lang.swe=Swedish -lang.syr=Syriac -lang.tam=Tamil -lang.tat=Tatar -lang.tel=Telugu -lang.tgk=Tajik -lang.tgl=Tagalog -lang.tha=Thai -lang.tir=Tigrinya -lang.ton=Tonga (Tonga Islands) -lang.tur=Turkish -lang.uig=Uighur, Uyghur -lang.ukr=Ukrainian -lang.urd=Urdu -lang.uzb=Uzbek -lang.uzb_cyrl=Uzbek (Cyrillic) -lang.vie=Vietnamese -lang.yid=Yiddish +lang.afr=Afrikaanca +lang.amh=Amharca +lang.ara=Arapça +lang.asm=Assamca +lang.aze=Azerice +lang.aze_cyrl=Azerice (Kiril) +lang.bel=Beyaz Rusça (Belarusça) +lang.ben=Bengalce +lang.bod=Tibetçe +lang.bos=Boşnakça +lang.bre=Bretonca +lang.bul=Bulgarca +lang.cat=Katalanca +lang.ceb=Cebuano +lang.ces=Çekçe +lang.chi_sim=Çince (Basitleştirilmiş) +lang.chi_sim_vert=Çince (Basitleştirilmiş, Dikey) +lang.chi_tra=Çince (Geleneksel) +lang.chi_tra_vert=Çince (Geleneksel, Dikey) +lang.chr=Çerokice +lang.cos=Korsikaca +lang.cym=Gallerce (Galce) +lang.dan=Danca +lang.dan_frak=Danca (Fraktur) +lang.deu=Almanca +lang.deu_frak=Almanca (Fraktur) +lang.div=Maldivce (Divehi) +lang.dzo=Dzongkha +lang.ell=Yunanca +lang.eng=İngilizce +lang.enm=İngilizce, Orta Çağ (1100-1500) +lang.epo=Esperanto +lang.equ=Matematik / denklem tanıma modülü +lang.est=Estonca +lang.eus=Baskça +lang.fao=Faroece +lang.fas=Farsça +lang.fil=Filipince +lang.fin=Fince +lang.fra=Fransızca +lang.frk=Frankça +lang.frm=Fransızca, Orta Çağ (yaklaşık 1400-1600) +lang.fry=Batı Frizce +lang.gla=İskoç Galcesi +lang.gle=İrlandaca +lang.glg=Galiçyaca +lang.grc=Antik Yunanca +lang.guj=Gujaratça +lang.hat=Haiti Creole +lang.heb=İbranice +lang.hin=Hintçe +lang.hrv=Hırvatça +lang.hun=Macarca +lang.hye=Ermenice +lang.iku=İnuktitut +lang.ind=Endonezce +lang.isl=İzlandaca +lang.ita=İtalyanca +lang.ita_old=İtalyanca (Eski) +lang.jav=Cava dili +lang.jpn=Japonca +lang.jpn_vert=Japonca (Dikey) +lang.kan=Kannada +lang.kat=Gürcüce +lang.kat_old=Gürcüce (Eski) +lang.kaz=Kazakça +lang.khm=Merkez Khmer dili +lang.kir=Kırgızca +lang.kmr=Kuzey Kürtçesi +lang.kor=Korece +lang.kor_vert=Korece (Dikey) +lang.lao=Laosça +lang.lat=Latince +lang.lav=Letonca +lang.lit=Litvanca +lang.ltz=Lüksemburgca +lang.mal=Malayalamca +lang.mar=Marathi +lang.mkd=Makedonca +lang.mlt=Maltaca +lang.mon=Moğolca +lang.mri=Maorice +lang.msa=Malayca +lang.mya=Birmanca (Burma) +lang.nep=Nepalce +lang.nld=Hollandaca; Flamanca +lang.nor=Norveççe +lang.oci=Oksitanca (1500 sonrası) +lang.ori=Oriya +lang.osd=Yönlendirme ve yazı tipi algılama modülü +lang.pan=Pencapça +lang.pol=Lehçe (Polonyaca) +lang.por=Portekizce +lang.pus=Peştuca +lang.que=Keçuva dili +lang.ron=Rumence, Moldovca +lang.rus=Rusça +lang.san=Sanskritçe +lang.sin=Seylanca (Sinhala) +lang.slk=Slovakça +lang.slk_frak=Slovakça (Fraktur) +lang.slv=Slovence +lang.snd=Sindhice +lang.spa=İspanyolca +lang.spa_old=İspanyolca (Eski) +lang.sqi=Arnavutça +lang.srp=Sırpça +lang.srp_latn=Sırpça (Latin alfabesiyle) +lang.sun=Sundaca +lang.swa=Svahili dili +lang.swe=İsveççe +lang.syr=Süryanice +lang.tam=Tamilce +lang.tat=Tatarca +lang.tel=Telugu +lang.tgk=Tacikçe +lang.tgl=Tagalog +lang.tha=Tayca +lang.tir=Tigrinya +lang.ton=Tonga dili (Tonga Adaları) +lang.tur=Türkçe +lang.uig=Uygurca +lang.ukr=Ukraynaca +lang.urd=Urduca +lang.uzb=Özbekçe +lang.uzb_cyrl=Özbekçe (Kiril) +lang.vie=Vietnamca +lang.yid=Yidiş lang.yor=Yoruba addPageNumbers.fontSize=Font Büyüklüğü @@ -146,8 +146,8 @@ uploadLimit=Maksimum dosya boyutu: uploadLimitExceededSingular=çok büyük. İzin verilen maksimum boyut: uploadLimitExceededPlural=çok büyük. İzin verilen maksimum boyut: processTimeWarning=Uyarı: Bu işlem, dosya boyutuna bağlı olarak bir dakikaya kadar sürebilir. -pageOrderPrompt=Özel Sayfa Sırası (Virgülle ayrılmış sayfa numaraları veya 2n+1 gibi bir fonksiyon girin) : -pageSelectionPrompt=Özel Sayfa Seçimi (1,5,6 sayfa numaralarının virgülle ayrılmış bir listesini veya 2n+1 gibi bir fonksiyon girin) : +pageOrderPrompt=Özel Sayfa Sırası (Virgülle ayrılmış sayfa numaraları veya 2n+1 gibi bir fonksiyon girin): +pageSelectionPrompt=Özel Sayfa Seçimi (1,5,6 sayfa numaralarının virgülle ayrılmış bir listesini veya 2n+1 gibi bir fonksiyon girin): goToPage=Sayfaya Git true=Doğru false=Yanlış @@ -170,67 +170,67 @@ sizes.medium=Orta sizes.large=Büyük sizes.x-large=Çok Büyük error.pdfPassword=PDF belgesi şifreli ve şifre ya sağlanmadı ya da yanlış. -error.pdfCorrupted=PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation. -error.pdfCorruptedMultiple=One or more PDF files appear to be corrupted or damaged. Please try using the 'Repair PDF' feature on each file first before attempting to merge them. -error.pdfCorruptedDuring=Error {0}: PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation. +error.pdfCorrupted=PDF dosyası bozuk veya hasarlı görünüyor. Lütfen bu işlemi gerçekleştirmeden önce dosyayı düzeltmek için 'PDF Onar' özelliğini kullanmayı deneyin. +error.pdfCorruptedMultiple=Bir veya daha fazla PDF dosyası bozuk veya hasarlı görünüyor. Lütfen birleştirmeye çalışmadan önce her dosya için 'PDF Onar' özelliğini kullanmayı deneyin. +error.pdfCorruptedDuring=Hata {0}: PDF dosyası bozuk veya hasarlı görünüyor. Lütfen bu işlemi gerçekleştirmeden önce dosyayı düzeltmek için 'PDF Onar' özelliğini kullanmayı deneyin. # Frontend corruption error messages -error.pdfInvalid=The PDF file "{0}" appears to be corrupted or has an invalid structure. Please try using the 'Repair PDF' feature to fix the file before proceeding. -error.tryRepair=Try using the Repair PDF feature to fix corrupted files. +error.pdfInvalid="{0}" adlı PDF dosyası bozuk görünüyor veya geçersiz bir yapıya sahip. Lütfen işlemi gerçekleştirmeden önce dosyayı düzeltmek için 'PDF Onar' özelliğini kullanmayı deneyin. +error.tryRepair=Bozuk dosyaları düzeltmek için PDF Onar özelliğini kullanmayı deneyin. # Additional error messages -error.pdfEncryption=The PDF appears to have corrupted encryption data. This can happen when the PDF was created with incompatible encryption methods. Please try using the 'Repair PDF' feature first, or contact the document creator for a new copy. -error.fileProcessing=An error occurred while processing the file during {0} operation: {1} +error.pdfEncryption=PDF dosyasının şifreleme verileri bozulmuş görünüyor. Bu, PDF uyumsuz şifreleme yöntemleriyle oluşturulduğunda meydana gelebilir. Lütfen önce 'PDF Onar' özelliğini kullanmayı deneyin veya belgenin oluşturucusuyla iletişime geçerek yeni bir kopya isteyin. +error.fileProcessing={0} işlemi sırasında dosya işlenirken bir hata oluştu: {1} # Generic error message templates -error.toolNotInstalled={0} is not installed -error.toolRequired={0} is required for {1} -error.conversionFailed={0} conversion failed -error.commandFailed={0} command failed -error.algorithmNotAvailable={0} algorithm not available -error.optionsNotSpecified={0} options are not specified -error.fileFormatRequired=File must be in {0} format -error.invalidFormat=Invalid {0} format: {1} -error.endpointDisabled=This endpoint has been disabled by the admin -error.urlNotReachable=URL is not reachable, please provide a valid URL +error.toolNotInstalled={0} yüklü değil +error.toolRequired={1} işlemi için {0} gereklidir +error.conversionFailed={0} dönüştürme işlemi başarısız oldu +error.commandFailed={0} komutu başarısız oldu +error.algorithmNotAvailable={0} algoritması kullanılamıyor +error.optionsNotSpecified={0} seçenekleri belirtilmemiş +error.fileFormatRequired=Dosya {0} formatında olmalıdır +error.invalidFormat=Geçersiz {0} formatı: {1} +error.endpointDisabled=Bu uç nokta yönetici tarafından devre dışı bırakılmıştır +error.urlNotReachable=URL erişilebilir değil, lütfen geçerli bir URL sağlayın # DPI and image rendering messages - used by frontend for dynamic translation # Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message # Frontend parses this and replaces with localized versions using these keys -error.dpiExceedsLimit=DPI value {0} exceeds maximum safe limit of {1}. High DPI values can cause memory issues and crashes. Please use a lower DPI value. -error.pageTooBigForDpi=PDF page {0} is too large to render at {1} DPI. Please try a lower DPI value (recommended: 150 or less). -error.pageTooBigExceedsArray=PDF page {0} is too large to render at {1} DPI. The resulting image would exceed Java's maximum array size. Please try a lower DPI value (recommended: 150 or less). -error.pageTooBigFor300Dpi=PDF page {0} is too large to render at 300 DPI. The resulting image would exceed Java's maximum array size. Please use a lower DPI value for PDF-to-image conversion. +error.dpiExceedsLimit=DPI değeri {0}, maksimum güvenli sınır olan {1} değerini aşıyor. Yüksek DPI değerleri bellek sorunlarına ve çökme hatalarına neden olabilir. Lütfen daha düşük bir DPI değeri kullanın. +error.pageTooBigForDpi=PDF sayfası {0}, {1} DPI değerinde işlenemeyecek kadar büyük. Lütfen daha düşük bir DPI değeri deneyin (önerilen: 150 veya daha az). +error.pageTooBigExceedsArray=PDF sayfası {0}, {1} DPI değerinde işlenemeyecek kadar büyük. Ortaya çıkan görüntü Java'nın maksimum dizi boyutunu aşacaktır. Lütfen daha düşük bir DPI değeri deneyin (önerilen: 150 veya daha az). +error.pageTooBigFor300Dpi=PDF sayfası {0}, 300 DPI değerinde işlenemeyecek kadar büyük. Ortaya çıkan görüntü Java'nın maksimum dizi boyutunu aşacaktır. Lütfen PDF'den görüntüye dönüştürme işlemi için daha düşük bir DPI değeri kullanın. # URL and website conversion messages # System requirements messages # Authentication and security messages -error.apiKeyInvalid=API key is not valid. -error.userNotFound=User not found. -error.passwordRequired=Password must not be null. -error.accountLocked=Your account has been locked due to too many failed login attempts. -error.invalidEmail=Invalid email addresses provided. -error.emailAttachmentRequired=An attachment is required to send the email. -error.signatureNotFound=Signature file not found. +error.apiKeyInvalid=API anahtarı geçerli değil. +error.userNotFound=Kullanıcı bulunamadı. +error.passwordRequired=Parola boş bırakılamaz. +error.accountLocked=Çok fazla başarısız giriş denemesi nedeniyle hesabınız kilitlendi. +error.invalidEmail=Geçersiz e-posta adresleri sağlandı. +error.emailAttachmentRequired=E-posta gönderebilmek için bir ek dosya gereklidir. +error.signatureNotFound=İmza dosyası bulunamadı. # File processing messages -error.fileNotFound=File not found with ID: {0} +error.fileNotFound=Dosya bulunamadı. Dosya kimliği: {0} # Database and configuration messages -error.noBackupScripts=No backup scripts were found. -error.unsupportedProvider={0} is not currently supported. -error.pathTraversalDetected=Path traversal detected for security reasons. +error.noBackupScripts=Yedekleme betikleri bulunamadı. +error.unsupportedProvider={0} şu anda desteklenmiyor. +error.pathTraversalDetected=Güvenlik nedeniyle yol geçişi (path traversal) tespit edildi. # Validation messages -error.invalidArgument=Invalid argument: {0} -error.argumentRequired={0} must not be null -error.operationFailed=Operation failed: {0} -error.angleNotMultipleOf90=Angle must be a multiple of 90 -error.pdfBookmarksNotFound=No PDF bookmarks/outline found in document -error.fontLoadingFailed=Error processing font file -error.fontDirectoryReadFailed=Failed to read font directory +error.invalidArgument=Geçersiz argüman: {0} +error.argumentRequired={0} boş olamaz +error.operationFailed=İşlem başarısız oldu: {0} +error.angleNotMultipleOf90=Açı 90'ın katı olmalıdır +error.pdfBookmarksNotFound=Belgede herhangi bir PDF yer imi / içindekiler bulunamadı +error.fontLoadingFailed=Yazı tipi dosyası işlenirken hata oluştu +error.fontDirectoryReadFailed=Yazı tipi dizini okunamadı delete=Sil username=Kullanıcı Adı password=Parola @@ -260,7 +260,7 @@ disabledCurrentUserMessage=Mevcut kullanıcı devre dışı bırakılamaz downgradeCurrentUserLongMessage=Mevcut kullanıcının rolü düşürülemiyor. Bu nedenle, mevcut kullanıcı gösterilmeyecektir. userAlreadyExistsOAuthMessage=Kullanıcı zaten bir OAuth2 kullanıcısı olarak mevcut. userAlreadyExistsWebMessage=Kullanıcı zaten bir web kullanıcısı olarak mevcut. -invalidRoleMessage=Invalid role. +invalidRoleMessage=Geçersiz rol. error=Hata oops=Tüh! help=Yardım @@ -273,7 +273,7 @@ color=Renk sponsor=Bağış info=Bilgi pro=Pro -proFeatures=Pro Features +proFeatures=Pro Özellikler page=Sayfa pages=Sayfalar loading=Yükleniyor... @@ -281,11 +281,11 @@ addToDoc=Dökümana Ekle reset=Sıfırla apply=Uygula noFileSelected=Hiçbir dosya seçilmedi. Lütfen bir dosya yükleyin. -view=View +view=Görüntüle cancel=İptal -back.toSettings=Ayarlar’a Geri Dön -back.toHome=Ana Sayfa’ya Geri Dön +back.toSettings=Ayarlar'a Geri Dön +back.toHome=Ana Sayfa'ya Geri Dön back.toAdmin=Yönetim Paneline Geri Dön legal.privacy=Gizlilik Politikası @@ -327,15 +327,15 @@ enterpriseEdition.button=Pro Sürümüne Yükselt enterpriseEdition.warning=Bu özellik yalnızca Pro kullanıcılarına sunulmaktadır. enterpriseEdition.yamlAdvert=Stirling PDF Pro, YAML yapılandırma dosyalarını ve diğer SSO özelliklerini destekler. enterpriseEdition.ssoAdvert=Daha fazla kullanıcı yönetimi özelliği mi arıyorsunuz? Stirling PDF Pro'ya göz atın -enterpriseEdition.proTeamFeatureDisabled=Team management features require a Pro licence or higher +enterpriseEdition.proTeamFeatureDisabled=Takım yönetimi özellikleri Pro lisansı veya daha üstü gerektirir ################# # Analytics # ################# -analytics.title=Stirling PDF’i daha iyi hale getirmek ister misiniz? +analytics.title=Stirling PDF'i daha iyi hale getirmek ister misiniz? analytics.paragraph1=Stirling PDF, ürünü geliştirmemize yardımcı olmak için isteğe bağlı analizleri içerir. Kişisel bilgileri veya dosya içeriklerini asla takip etmiyoruz. -analytics.paragraph2=Stirling PDF’in büyümesine destek olmak ve kullanıcılarımızı daha iyi anlayabilmemiz için analizleri etkinleştirmeyi düşünebilirsiniz. +analytics.paragraph2=Stirling PDF'in büyümesine destek olmak ve kullanıcılarımızı daha iyi anlayabilmemiz için analizleri etkinleştirmeyi düşünebilirsiniz. analytics.enable=Analizi Etkinleştir analytics.disable=Analizi Devre Dışı Bırak analytics.settings=Analiz ayarlarını config/settings.yml dosyasından değiştirebilirsiniz @@ -345,7 +345,7 @@ analytics.settings=Analiz ayarlarını config/settings.yml dosyasından değişt # NAVBAR # ############# navbar.favorite=Favoriler -navbar.recent=New and recently updated +navbar.recent=Yeni ve son güncellenenler navbar.darkmode=Karanlık Mod navbar.language=Diller navbar.settings=Ayarlar @@ -368,36 +368,36 @@ settings.update=Güncelleme mevcut settings.updateAvailable={0} mevcut kurulu sürümdür. Yeni bir sürüm ({1}) mevcuttur. # Update modal and notification strings -update.urgentUpdateAvailable=🚨 Update Available -update.updateAvailable=Update Available -update.modalTitle=Update Available -update.current=Current -update.latest=Latest -update.latestStable=Latest Stable -update.priority=Priority -update.recommendedAction=Recommended Action -update.breakingChangesDetected=⚠️ Breaking Changes Detected -update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below. -update.migrationGuides=Migration Guides: -update.viewGuide=View Guide -update.loadingDetailedInfo=Loading detailed version information... -update.close=Close -update.viewAllReleases=View All Releases -update.downloadLatest=Download Latest -update.availableUpdates=Available Updates: -update.unableToLoadDetails=Unable to load detailed version information. -update.version=Version +update.urgentUpdateAvailable=🚨 Güncelleme Mevcut +update.updateAvailable=Güncelleme Mevcut +update.modalTitle=Güncelleme Mevcut +update.current=Mevcut +update.latest=En Yeni +update.latestStable=En Yeni Kararlı +update.priority=Öncelik +update.recommendedAction=Önerilen İşlem +update.breakingChangesDetected=⚠️ Kırıcı Değişiklikler Tespit Edildi +update.breakingChangesMessage=Bu güncelleme kırıcı değişiklikler içeriyor. Lütfen aşağıdaki geçiş kılavuzlarını inceleyin. +update.migrationGuides=Geçiş Kılavuzları: +update.viewGuide=Kılavuzu Görüntüle +update.loadingDetailedInfo=Ayrıntılı sürüm bilgileri yükleniyor... +update.close=Kapat +update.viewAllReleases=Tüm Sürümleri Görüntüle +update.downloadLatest=En Yeniyi İndir +update.availableUpdates=Mevcut Güncellemeler: +update.unableToLoadDetails=Ayrıntılı sürüm bilgileri yüklenemedi. +update.version=Sürüm # Update priority levels -update.priority.urgent=URGENT +update.priority.urgent=ACİL update.priority.normal=NORMAL -update.priority.minor=MINOR -update.priority.low=LOW +update.priority.minor=ÖNEMSİZ +update.priority.low=DÜŞÜK # Breaking changes text -update.breakingChanges=Breaking Changes: -update.breakingChangesDefault=This version contains breaking changes -update.migrationGuide=Migration Guide +update.breakingChanges=Kırıcı Değişiklikler: +update.breakingChangesDefault=Bu sürüm kırıcı değişiklikler içeriyor +update.migrationGuide=Geçiş Kılavuzu settings.appVersion=Uygulama Sürümü: settings.downloadOption.title=İndirme seçeneği seçin (Zip olmayan tek dosya indirmeler için): settings.downloadOption.1=Aynı pencerede aç @@ -472,18 +472,18 @@ adminUserSettings.disabledUsers=Devre Dışı Kullanıcılar: adminUserSettings.totalUsers=Toplam Kullanıcılar: adminUserSettings.lastRequest=Son İstek adminUserSettings.usage=Kullanımı Görüntüle -adminUserSettings.teams=View/Edit Teams -adminUserSettings.team=Team -adminUserSettings.manageTeams=Manage Teams -adminUserSettings.createTeam=Create Team -adminUserSettings.viewTeam=View Team -adminUserSettings.deleteTeam=Delete Team -adminUserSettings.teamName=Team Name -adminUserSettings.teamExists=Team already exists -adminUserSettings.teamCreated=Team created successfully -adminUserSettings.teamChanged=User's team was updated -adminUserSettings.teamHidden=Hidden -adminUserSettings.totalMembers=Total Members +adminUserSettings.teams=Takımları Görüntüle/Düzenle +adminUserSettings.team=Takım +adminUserSettings.manageTeams=Takımları Yönet +adminUserSettings.createTeam=Takım Oluştur +adminUserSettings.viewTeam=Takımı Görüntüle +adminUserSettings.deleteTeam=Takımı Sil +adminUserSettings.teamName=Takım Adı +adminUserSettings.teamExists=Takım zaten mevcut +adminUserSettings.teamCreated=Takım başarıyla oluşturuldu +adminUserSettings.teamChanged=Kullanıcının takımı güncellendi +adminUserSettings.teamHidden=Gizli +adminUserSettings.totalMembers=Toplam Üye adminUserSettings.confirmDeleteTeam=Bu takımı silmek istediğinizden emin misiniz? teamCreated=Takım başarıyla oluşturuldu @@ -538,7 +538,7 @@ endpointStatistics.home=Ana Sayfa endpointStatistics.login=Giriş endpointStatistics.top=En Çok endpointStatistics.numberOfVisits=Ziyaret Sayısı -endpointStatistics.visitsTooltip=Ziyaret: {0} (toplamın %{1}’i) +endpointStatistics.visitsTooltip=Ziyaret: {0} (toplamın %{1}'i) endpointStatistics.retry=Yeniden Dene database.title=Veri Tabanını İçe/Dışa Aktar @@ -617,9 +617,9 @@ home.addImage.title=Resim Ekle home.addImage.desc=PDF'e belirli bir konuma resim ekler addImage.tags=img,jpg,fotoğraf,resim -home.attachments.title=Add Attachments -home.attachments.desc=Add or remove embedded files (attachments) to/from a PDF -attachments.tags=embed,attach,file,attachment,attachments +home.attachments.title=Ekleri Ekle +home.attachments.desc=PDF'ye gömülü dosyalar (ekler) ekle veya kaldır +attachments.tags=gömme,ekle,dosya,ek,ekler home.watermark.title=Filigran Ekle home.watermark.desc=PDF belgenize özel bir filigran ekleyin. @@ -772,21 +772,21 @@ home.HTMLToPDF.desc=Herhangi bir HTML dosyasını veya zip'i PDF'e dönüştür HTMLToPDF.tags=biçimlendirme,web-içeriği,dönüşüm,dönüştür #eml-to-pdf -home.EMLToPDF.title=Email to PDF -home.EMLToPDF.desc=Converts email (EML) files to PDF format including headers, body, and inline images -EMLToPDF.tags=email,conversion,eml,message,transformation,convert,mail +home.EMLToPDF.title=E-postayı PDF'ye Dönüştür +home.EMLToPDF.desc=Başlıklar, gövde ve satır içi resimler dahil olmak üzere e-posta (EML) dosyalarını PDF formatına dönüştürür +EMLToPDF.tags=e-posta, dönüşüm, eml, mesaj, dönüşüm, dönüştür, posta -EMLToPDF.title=Email To PDF -EMLToPDF.header=Email To PDF -EMLToPDF.submit=Convert -EMLToPDF.downloadHtml=Download HTML intermediate file instead of PDF -EMLToPDF.downloadHtmlHelp=This allows you to see the HTML version before PDF conversion and can help debug formatting issues -EMLToPDF.includeAttachments=Include attachments in PDF -EMLToPDF.maxAttachmentSize=Maximum attachment size (MB) -EMLToPDF.help=Converts email (EML) files to PDF format including headers, body, and inline images -EMLToPDF.troubleshootingTip1=Email to HTML is a more reliable process, so with batch-processing it is recommended to save both -EMLToPDF.troubleshootingTip2=With a small number of Emails, if the PDF is malformed, you can download HTML and override some of the problematic HTML/CSS code. -EMLToPDF.troubleshootingTip3=Embeddings, however, do not work with HTMLs +EMLToPDF.title=E-postayı PDF'ye Dönüştür +EMLToPDF.header=E-postayı PDF'ye Dönüştür +EMLToPDF.submit=Dönüştür +EMLToPDF.downloadHtml=PDF yerine HTML ara dosyasını indir +EMLToPDF.downloadHtmlHelp=Bu, PDF dönüşümünden önce HTML sürümünü görmenizi sağlar ve biçimlendirme sorunlarını çözmeye yardımcı olabilir +EMLToPDF.includeAttachments=PDF'ye ekleri dahil et +EMLToPDF.maxAttachmentSize=Maksimum ek boyutu (MB) +EMLToPDF.help=Başlıklar, gövde ve satır içi resimler dahil olmak üzere e-posta (EML) dosyalarını PDF formatına dönüştürür +EMLToPDF.troubleshootingTip1=E-postayı HTML'ye dönüştürmek daha güvenilir bir işlemdir, bu nedenle toplu işleme yaparken her ikisini de kaydetmek önerilir +EMLToPDF.troubleshootingTip2=Az sayıda e-posta için, PDF bozuksa HTML dosyasını indirip bazı sorunlu HTML/CSS kodlarını değiştirebilirsiniz +EMLToPDF.troubleshootingTip3=Ancak, gömülü içerikler HTML ile çalışmaz home.MarkdownToPDF.title=Markdown'dan PDF'e home.MarkdownToPDF.desc=Herhangi bir Markdown dosyasını PDF'e dönüştürür @@ -907,8 +907,8 @@ login.userIsDisabled=Kullanıcı devre dışı bırakıldı, şu anda bu kullan login.alreadyLoggedIn=Zaten şu cihazlarda oturum açılmış: login.alreadyLoggedIn2=Lütfen bu cihazlardan çıkış yaparak tekrar deneyin. login.toManySessions=Çok fazla aktif oturumunuz var -login.logoutMessage=You have been logged out. -login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator. +login.logoutMessage=Oturumunuz kapatıldı. +login.invalidInResponseTo=İstenen SAML yanıtı geçersiz veya süresi dolmuş. Lütfen yöneticiyle iletişime geçin. #auto-redact autoRedact.title=Otomatik Karartma @@ -974,28 +974,28 @@ getPdfInfo.title=PDF Hakkında Bilgi Al getPdfInfo.header=PDF Hakkında Bilgi Al getPdfInfo.submit=Bilgi Al getPdfInfo.downloadJson=JSON İndir -getPdfInfo.summary=PDF Summary -getPdfInfo.summary.encrypted=This PDF is encrypted so may face issues with some applications -getPdfInfo.summary.permissions=This PDF has {0} restricted permissions which may limit what you can do with it -getPdfInfo.summary.compliance=This PDF complies with the {0} standard -getPdfInfo.summary.basicInfo=Basic Information -getPdfInfo.summary.docInfo=Document Information -getPdfInfo.summary.encrypted.alert=Encrypted PDF - This document is password protected -getPdfInfo.summary.not.encrypted.alert=Unencrypted PDF - No password protection -getPdfInfo.summary.permissions.alert=Restricted Permissions - {0} actions are not allowed -getPdfInfo.summary.all.permissions.alert=All Permissions Allowed -getPdfInfo.summary.compliance.alert={0} Compliant -getPdfInfo.summary.no.compliance.alert=No Compliance Standards -getPdfInfo.summary.security.section=Security Status -getPdfInfo.section.BasicInfo=Basic Information about the PDF document including file size, page count, and language -getPdfInfo.section.Metadata=Document metadata including title, author, creation date and other document properties -getPdfInfo.section.DocumentInfo=Technical details about the PDF document structure and version -getPdfInfo.section.Compliancy=PDF standards compliance information (PDF/A, PDF/X, etc.) -getPdfInfo.section.Encryption=Security and encryption details of the document -getPdfInfo.section.Permissions=Document permission settings that control what actions can be performed -getPdfInfo.section.Other=Additional document components like bookmarks, layers, and embedded files -getPdfInfo.section.FormFields=Interactive form fields present in the document -getPdfInfo.section.PerPageInfo=Detailed information about each page in the document +getPdfInfo.summary=PDF Özeti +getPdfInfo.summary.encrypted=Bu PDF şifreli olduğu için bazı uygulamalarda sorun yaşanabilir +getPdfInfo.summary.permissions=Bu PDF'de {0} kısıtlanmış izinler var, bu da yapabileceklerinizi sınırlayabilir +getPdfInfo.summary.compliance=Bu PDF {0} standardına uygundur +getPdfInfo.summary.basicInfo=Temel Bilgiler +getPdfInfo.summary.docInfo=Belge Bilgileri +getPdfInfo.summary.encrypted.alert=Şifreli PDF - Bu belge parola korumalıdır +getPdfInfo.summary.not.encrypted.alert=Şifresiz PDF - Parola koruması yok +getPdfInfo.summary.permissions.alert=Kısıtlanmış İzinler - {0} işlem izin verilmemiştir +getPdfInfo.summary.all.permissions.alert=Tüm İzinler Verildi +getPdfInfo.summary.compliance.alert={0} Uygun +getPdfInfo.summary.no.compliance.alert=Uygunluk Standardı Yok +getPdfInfo.summary.security.section=Güvenlik Durumu +getPdfInfo.section.BasicInfo=PDF belgesinin dosya boyutu, sayfa sayısı ve dili dahil temel bilgileri +getPdfInfo.section.Metadata=Başlık, yazar, oluşturulma tarihi ve diğer belge özelliklerini içeren belge meta verisi +getPdfInfo.section.DocumentInfo=PDF belge yapısı ve sürümü hakkında teknik detaylar +getPdfInfo.section.Compliancy=PDF standartlarına uygunluk bilgisi (PDF/A, PDF/X, vb.) +getPdfInfo.section.Encryption=Belgenin güvenlik ve şifreleme detayları +getPdfInfo.section.Permissions=Hangi işlemlerin yapılabileceğini kontrol eden belge izin ayarları +getPdfInfo.section.Other=Yer imleri, katmanlar ve gömülü dosyalar gibi ek belge bileşenleri +getPdfInfo.section.FormFields=Belgede bulunan etkileşimli form alanları +getPdfInfo.section.PerPageInfo=Belgedeki her sayfa hakkında ayrıntılı bilgiler #markdown-to-pdf @@ -1007,9 +1007,9 @@ MarkdownToPDF.credit=WeasyPrint Kullanıyor #pdf-to-markdown -PDFToMarkdown.title=PDF To Markdown -PDFToMarkdown.header=PDF To Markdown -PDFToMarkdown.submit=Convert +PDFToMarkdown.title=PDF'den Markdown'a +PDFToMarkdown.header=PDF'den Markdown'a +PDFToMarkdown.submit=Dönüştür #url-to-pdf @@ -1136,7 +1136,7 @@ pageLayout.submit=Gönder scalePages.title=Sayfa Ölçeğini Ayarla scalePages.header=Sayfa Ölçeğini Ayarla scalePages.pageSize=Belgenin bir sayfa boyutu. -scalePages.keepPageSize=Original Size +scalePages.keepPageSize=Orijinal Boyut scalePages.scaleFactor=Bir sayfanın yakınlaştırma seviyesi (kırpma). scalePages.submit=Gönder @@ -1156,7 +1156,7 @@ certSign.showSig=İmzayı Göster certSign.reason=Neden certSign.location=Konum certSign.name=İsim -certSign.showLogo=Show Logo +certSign.showLogo=Logoyu Göster certSign.submit=PDF'i İmzala @@ -1171,7 +1171,7 @@ removeCertSign.submit=İmzayı Kaldır removeBlanks.title=Boşları Kaldır removeBlanks.header=Boş Sayfaları Kaldır removeBlanks.threshold=Pixel Beyazlık Eşiği: -removeBlanks.thresholdDesc=Bir beyaz pixelin 'Beyaz' olarak sınıflandırılması için ne kadar beyaz olması gerektiğini belirlemek için eşik. 0 = Siyah, 255 saf beyaz. +removeBlanks.thresholdDesc=Bir beyaz pixelin 'Beyaz' olarak sınıflandırılması için ne kadar beyaz olması gerektiğini belirlemek için eşik. 0=Siyah, 255 saf beyaz. removeBlanks.whitePercent=Beyaz Yüzde (%): removeBlanks.whitePercentDesc=Bir sayfanın 'beyaz' pixel olması gereken yüzdesi removeBlanks.submit=Boşları Kaldır @@ -1287,8 +1287,8 @@ compress.title=Sıkıştır compress.header=PDF'i Sıkıştır compress.credit=Bu hizmet PDF Sıkıştırma/Optimizasyonu için qpdf kullanır. compress.grayscale.label=Sıkıştırma için Gri Ton Uygula -compress.selectText.1=Compression Settings -compress.selectText.1.1=1-3 PDF compression,
4-6 lite image compression,
7-9 intense image compression Will dramatically reduce image quality +compress.selectText.1=Sıkıştırma Ayarları +compress.selectText.1.1=1-3 PDF sıkıştırma,
4-6 hafif görüntü sıkıştırma,
7-9 yoğun görüntü sıkıştırma Görüntü kalitesini ciddi şekilde düşürecektir compress.selectText.2=Optimizasyon seviyesi: compress.selectText.4=Otomatik mod - PDF'in tam boyutuna ulaşmak için kaliteyi otomatik ayarlar compress.selectText.5=Beklenen PDF Boyutu (örn. 25MB, 10.8MB, 25KB) @@ -1303,11 +1303,11 @@ addImage.upload=Resim ekle addImage.submit=Resim ekle #attachments -attachments.title=Add Attachments -attachments.header=Add attachments -attachments.description=Allows you to add attachments to the PDF -attachments.descriptionPlaceholder=Enter a description for the attachments... -attachments.addButton=Add Attachments +attachments.title=Ekler Ekle +attachments.header=Ekler Ekle +attachments.description=PDF'ye ekler eklemenizi sağlar +attachments.descriptionPlaceholder=Ekler için bir açıklama girin... +attachments.addButton=Ekleri Ekle #merge merge.title=Birleştir @@ -1315,7 +1315,7 @@ merge.header=Çoklu PDF'leri Birleştir (2+) merge.sortByName=İsme göre sırala merge.sortByDate=Tarihe göre sırala merge.removeCertSign=Birleştirilen dosyadaki dijital imza kaldırılsın mı? -merge.generateToc=Generate table of contents in the merged file? +merge.generateToc=Birleştirilen dosyada içindekiler tablosu oluşturulsun mu? merge.submit=Birleştir @@ -1435,7 +1435,7 @@ pdfToImage.colorType=Renk türü pdfToImage.color=Renk pdfToImage.grey=Gri tonlama pdfToImage.blackwhite=Siyah ve Beyaz (Veri kaybolabilir!) -pdfToImage.dpi=DPI (The server limit is {0} dpi) +pdfToImage.dpi=DPI (Sunucu limiti {0} dpi) pdfToImage.submit=Dönüştür pdfToImage.info=Python kurulu değil. WebP dönüşümü için gereklidir. pdfToImage.placeholder=(örneğin 1,2,8 veya 4,7,12-16 ya da 2n-1) @@ -1652,9 +1652,9 @@ survey.meeting.1=Eğer Stirling PDF'i iş yerinizde kullanıyorsanız, sizinle g survey.meeting.2=Bu fırsat sayesinde: survey.meeting.3=Kurulum, entegrasyonlar veya sorun giderme konularında yardım alabilirsiniz survey.meeting.4=Performans, uç durumlar ve eksik özellikler hakkında doğrudan geri bildirim sağlayabilirsiniz -survey.meeting.5=Stirling PDF’i gerçek dünya kurumsal kullanımı için daha iyi hale getirmemize yardımcı olabilirsiniz +survey.meeting.5=Stirling PDF'i gerçek dünya kurumsal kullanımı için daha iyi hale getirmemize yardımcı olabilirsiniz survey.meeting.6=İlgileniyorsanız, ekibimizden doğrudan zaman ayırabilirsiniz. (Yalnızca İngilizce) -survey.meeting.7=Kullanım senaryolarınızı dinlemeyi ve Stirling PDF’i daha da iyi hale getirmeyi sabırsızlıkla bekliyoruz! +survey.meeting.7=Kullanım senaryolarınızı dinlemeyi ve Stirling PDF'i daha da iyi hale getirmeyi sabırsızlıkla bekliyoruz! survey.meeting.notInterested=Kurumsal kullanıcı değilseniz ve/veya görüşmeye ilgi duymuyorsanız survey.meeting.button=Görüşme Planla @@ -1740,23 +1740,23 @@ validateSignature.cert.keySize=Anahtar Boyutu validateSignature.cert.version=Sürüm validateSignature.cert.keyUsage=Anahtar Kullanımı validateSignature.cert.selfSigned=Kendi Kendine İmzalı -validateSignature.cert.bits=bits +validateSignature.cert.bits=bit # Audit Dashboard -audit.dashboard.title=Audit Dashboard -audit.dashboard.systemStatus=Audit System Status +audit.dashboard.title=Denetim Kontrol Paneli +audit.dashboard.systemStatus=Denetim Sistemi Durumu audit.dashboard.status=Durum audit.dashboard.enabled=Etkin audit.dashboard.disabled=Devre Dışı -audit.dashboard.currentLevel=Current Level -audit.dashboard.retentionPeriod=Retention Period -audit.dashboard.days=days -audit.dashboard.totalEvents=Total Events +audit.dashboard.currentLevel=Mevcut Seviye +audit.dashboard.retentionPeriod=Saklama Süresi +audit.dashboard.days=gün +audit.dashboard.totalEvents=Toplam Olay # Audit Dashboard Tabs -audit.dashboard.tab.dashboard=Dashboard -audit.dashboard.tab.events=Audit Events -audit.dashboard.tab.export=Export +audit.dashboard.tab.dashboard=Gösterge Paneli +audit.dashboard.tab.events=Denetim Olayları +audit.dashboard.tab.export=Dışa Aktar # Dashboard Charts audit.dashboard.eventsByType=Türüne Göre Olaylar audit.dashboard.eventsByUser=Kullanıcıya Göre Olaylar @@ -1766,23 +1766,23 @@ audit.dashboard.period.30days=30 Gün audit.dashboard.period.90days=90 Gün # Events Tab -audit.dashboard.auditEvents=Audit Events -audit.dashboard.filter.eventType=Event Type -audit.dashboard.filter.allEventTypes=All event types -audit.dashboard.filter.user=User -audit.dashboard.filter.userPlaceholder=Filter by user -audit.dashboard.filter.startDate=Start Date -audit.dashboard.filter.endDate=End Date -audit.dashboard.filter.apply=Apply Filters -audit.dashboard.filter.reset=Reset Filters +audit.dashboard.auditEvents=Denetim Olayları +audit.dashboard.filter.eventType=Olay Türü +audit.dashboard.filter.allEventTypes=Tüm olay türleri +audit.dashboard.filter.user=Kullanıcı +audit.dashboard.filter.userPlaceholder=Kullanıcıya göre filtrele +audit.dashboard.filter.startDate=Başlangıç Tarihi +audit.dashboard.filter.endDate=Bitiş Tarihi +audit.dashboard.filter.apply=Filtreleri Uygula +audit.dashboard.filter.reset=Filtreleri Sıfırla # Table Headers audit.dashboard.table.id=ID -audit.dashboard.table.time=Time -audit.dashboard.table.user=User -audit.dashboard.table.type=Type -audit.dashboard.table.details=Details -audit.dashboard.table.viewDetails=View Details +audit.dashboard.table.time=Zaman +audit.dashboard.table.user=Kullanıcı +audit.dashboard.table.type=Tür +audit.dashboard.table.details=Detaylar +audit.dashboard.table.viewDetails=Detayları Görüntüle # Pagination audit.dashboard.pagination.show=Göster @@ -1792,10 +1792,10 @@ audit.dashboard.pagination.pageInfo2=/ audit.dashboard.pagination.totalRecords=Toplam kayıt: # Modal -audit.dashboard.modal.eventDetails=Event Details +audit.dashboard.modal.eventDetails=Olay Detayları audit.dashboard.modal.id=ID audit.dashboard.modal.user=Kullanıcı -audit.dashboard.modal.type=Type +audit.dashboard.modal.type=Tip audit.dashboard.modal.time=Zaman audit.dashboard.modal.data=Veri @@ -1824,8 +1824,8 @@ audit.dashboard.js.loadingPage=Sayfa yükleniyor # Cookie banner # #################### cookieBanner.popUp.title=Çerezleri Nasıl Kullanıyoruz -cookieBanner.popUp.description.1=Stirling PDF’yi sizin için daha iyi çalıştırmak için çerezler ve diğer teknolojileri kullanıyoruz — araçlarımızı geliştirmemize ve seveceğiniz özellikler oluşturmamıza yardımcı oluyorlar. -cookieBanner.popUp.description.2=İstemiyorsanız, ‘Hayır Teşekkürler’ butonuna tıklayarak yalnızca temel, gerekli çerezleri etkinleştirebilirsiniz. +cookieBanner.popUp.description.1=Stirling PDF'yi sizin için daha iyi çalıştırmak için çerezler ve diğer teknolojileri kullanıyoruz — araçlarımızı geliştirmemize ve seveceğiniz özellikler oluşturmamıza yardımcı oluyorlar. +cookieBanner.popUp.description.2=İstemiyorsanız, 'Hayır Teşekkürler' butonuna tıklayarak yalnızca temel, gerekli çerezleri etkinleştirebilirsiniz. cookieBanner.popUp.acceptAllBtn=Tamam cookieBanner.popUp.acceptNecessaryBtn=Hayır Teşekkürler cookieBanner.popUp.showPreferencesBtn=Tercihleri Yönet @@ -1846,20 +1846,20 @@ cookieBanner.preferencesModal.analytics.title=Analitik cookieBanner.preferencesModal.analytics.description=Bu çerezler, araçlarımızın nasıl kullanıldığını anlamamıza yardımcı olur, böylece topluluğumuzun en çok değer verdiği özellikleri geliştirmeye odaklanabiliriz. İçiniz rahat olsun — Stirling PDF, belgelerinizin içeriğini asla takip etmez ve etmeyecektir. #scannerEffect -scannerEffect.title=Scanner Effect -scannerEffect.header=Scanner Effect -scannerEffect.description=Create a PDF that looks like it was scanned -scannerEffect.selectPDF=Select PDF: -scannerEffect.quality=Scan Quality -scannerEffect.quality.low=Low -scannerEffect.quality.medium=Medium -scannerEffect.quality.high=High -scannerEffect.rotation=Rotation Angle -scannerEffect.rotation.none=None -scannerEffect.rotation.slight=Slight -scannerEffect.rotation.moderate=Moderate -scannerEffect.rotation.severe=Severe -scannerEffect.submit=Create Scanner Effect +scannerEffect.title=Tarayıcı Efekti +scannerEffect.header=Tarayıcı Efekti +scannerEffect.description=Taranmış gibi görünen bir PDF oluştur +scannerEffect.selectPDF=PDF Seç: +scannerEffect.quality=Tarama Kalitesi +scannerEffect.quality.low=Düşük +scannerEffect.quality.medium=Orta +scannerEffect.quality.high=Yüksek +scannerEffect.rotation=Döndürme Açısı +scannerEffect.rotation.none=Yok +scannerEffect.rotation.slight=Hafif +scannerEffect.rotation.moderate=Orta +scannerEffect.rotation.severe=Şiddetli +scannerEffect.submit=Tarayıcı Efekti Oluştur #home.scannerEffect home.scannerEffect.title=Sahte Tarama @@ -1893,12 +1893,12 @@ editTableOfContents.replaceExisting=Mevcut yer işaretlerini değiştir (var ola editTableOfContents.editorTitle=Yer İşareti Düzenleyici editTableOfContents.editorDesc=Aşağıdan yer işaretleri ekleyin ve düzenleyin. Alt yer işareti eklemek için + simgesine tıklayın. editTableOfContents.addBookmark=Yeni Yer İşareti Ekle -editTableOfContents.importBookmarksDefault=Import -editTableOfContents.importBookmarksFromJsonFile=Upload JSON file -editTableOfContents.importBookmarksFromClipboard=Paste from clipboard -editTableOfContents.exportBookmarksDefault=Export -editTableOfContents.exportBookmarksAsJson=Download as JSON -editTableOfContents.exportBookmarksAsText=Copy as text +editTableOfContents.importBookmarksDefault=İçe Aktar +editTableOfContents.importBookmarksFromJsonFile=JSON dosyası yükle +editTableOfContents.importBookmarksFromClipboard=Panodan yapıştır +editTableOfContents.exportBookmarksDefault=Dışa Aktar +editTableOfContents.exportBookmarksAsJson=JSON olarak indir +editTableOfContents.exportBookmarksAsText=Metin olarak kopyala editTableOfContents.desc.1=Bu araç, bir PDF belgesine içindekiler tablosu (yer işaretleri) eklemenizi veya mevcut olanları düzenlemenizi sağlar. editTableOfContents.desc.2=Alt yer işaretleri ekleyerek hiyerarşik bir yapı oluşturabilirsiniz. editTableOfContents.desc.3=Her yer işareti bir başlık ve hedef sayfa numarası gerektirir. From cec5d1e1b63151442eba84012241774ae8e7a007 Mon Sep 17 00:00:00 2001 From: Ludy Date: Sun, 24 Aug 2025 22:32:35 +0200 Subject: [PATCH 11/20] ci(workflow): simplify PR deployment by removing redundant repo/ref lookup (#4266) # Description of Changes - Removed the separate step that fetched PR repository and ref (`get-pr-info`). - Simplified checkout by directly using `refs/pull/${{ needs.check-comment.outputs.pr_number }}/merge`. - This reduces workflow complexity and avoids unnecessary API calls while still supporting forked PRs. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --- .../workflows/PR-Demo-Comment-with-react.yml | 28 +------------------ 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/.github/workflows/PR-Demo-Comment-with-react.yml b/.github/workflows/PR-Demo-Comment-with-react.yml index ff653ad15..51949b6cc 100644 --- a/.github/workflows/PR-Demo-Comment-with-react.yml +++ b/.github/workflows/PR-Demo-Comment-with-react.yml @@ -33,8 +33,6 @@ jobs: ) outputs: pr_number: ${{ steps.get-pr.outputs.pr_number }} - pr_repository: ${{ steps.get-pr-info.outputs.repository }} - pr_ref: ${{ steps.get-pr-info.outputs.ref }} comment_id: ${{ github.event.comment.id }} disable_security: ${{ steps.check-security-flag.outputs.disable_security }} enable_pro: ${{ steps.check-pro-flag.outputs.enable_pro }} @@ -66,29 +64,6 @@ jobs: console.log(`PR Number: ${prNumber}`); core.setOutput('pr_number', prNumber); - - name: Get PR repository and ref - id: get-pr-info - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 - with: - script: | - const { owner, repo } = context.repo; - const prNumber = context.payload.issue.number; - - const { data: pr } = await github.rest.pulls.get({ - owner, - repo, - pull_number: prNumber, - }); - - // For forks, use the full repository name, for internal PRs use the current repo - const repository = pr.head.repo.fork ? pr.head.repo.full_name : `${owner}/${repo}`; - - console.log(`PR Repository: ${repository}`); - console.log(`PR Branch: ${pr.head.ref}`); - - core.setOutput('repository', repository); - core.setOutput('ref', pr.head.ref); - - name: Check for security/login flag id: check-security-flag env: @@ -171,8 +146,7 @@ jobs: - name: Checkout PR uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 with: - repository: ${{ needs.check-comment.outputs.pr_repository }} - ref: ${{ needs.check-comment.outputs.pr_ref }} + ref: refs/pull/${{ needs.check-comment.outputs.pr_number }}/merge token: ${{ steps.setup-bot.outputs.token }} - name: Set up JDK From 4a28c64dee952d21e1ace6ca4896124fb903fc59 Mon Sep 17 00:00:00 2001 From: Ludy Date: Sun, 24 Aug 2025 22:38:07 +0200 Subject: [PATCH 12/20] fix(i18n): standardize `{filename}` placeholder in `addPageNumbers.customNumberDesc` across all translations (#4204) # Description of Changes - Standardized the placeholder for filename in `addPageNumbers.customNumberDesc` to `{filename}` across all affected translation files. - Fixed inconsistent or localized variations (e.g., `{filnavn}`, `{ime datoteke}`, `{nume_fisier}`, `{nome do arquivo}`, `{nama berkas}`) and ensured the placeholder is uniform. - Corrected missing closing braces and quotes in several language files. - Affected locales include: `ar_AR`, `az_AZ`, `bg_BG`, `da_DK`, `el_GR`, `en_GB`, `en_US`, `es_ES`, `ga_IE`, `hr_HR`, `id_ID`, `it_IT`, `nl_NL`, `no_NB`, `pl_PL`, `pt_BR`, `pt_PT`, `ro_RO`, `sk_SK`, `sl_SI`, `sr_LATN_RS`, `sv_SE`, `tr_TR`, `vi_VN`. # @Frooodle please merge after https://github.com/Stirling-Tools/Stirling-PDF/pull/4202 --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --- app/core/src/main/resources/messages_ar_AR.properties | 2 +- app/core/src/main/resources/messages_az_AZ.properties | 2 +- app/core/src/main/resources/messages_bg_BG.properties | 2 +- app/core/src/main/resources/messages_da_DK.properties | 2 +- app/core/src/main/resources/messages_el_GR.properties | 2 +- app/core/src/main/resources/messages_en_GB.properties | 2 +- app/core/src/main/resources/messages_en_US.properties | 2 +- app/core/src/main/resources/messages_es_ES.properties | 2 +- app/core/src/main/resources/messages_ga_IE.properties | 2 +- app/core/src/main/resources/messages_hr_HR.properties | 2 +- app/core/src/main/resources/messages_id_ID.properties | 2 +- app/core/src/main/resources/messages_it_IT.properties | 2 +- app/core/src/main/resources/messages_nl_NL.properties | 2 +- app/core/src/main/resources/messages_no_NB.properties | 2 +- app/core/src/main/resources/messages_pl_PL.properties | 2 +- app/core/src/main/resources/messages_pt_BR.properties | 2 +- app/core/src/main/resources/messages_pt_PT.properties | 2 +- app/core/src/main/resources/messages_ro_RO.properties | 2 +- app/core/src/main/resources/messages_sk_SK.properties | 2 +- app/core/src/main/resources/messages_sl_SI.properties | 2 +- app/core/src/main/resources/messages_sr_LATN_RS.properties | 2 +- app/core/src/main/resources/messages_sv_SE.properties | 2 +- app/core/src/main/resources/messages_tr_TR.properties | 2 +- app/core/src/main/resources/messages_vi_VN.properties | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) diff --git a/app/core/src/main/resources/messages_ar_AR.properties b/app/core/src/main/resources/messages_ar_AR.properties index ed0bc1228..fcea7789c 100644 --- a/app/core/src/main/resources/messages_ar_AR.properties +++ b/app/core/src/main/resources/messages_ar_AR.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=الصفحات المراد ترقيمها addPageNumbers.selectText.6=نص مخصص addPageNumbers.customTextDesc=نص مخصص addPageNumbers.numberPagesDesc=أي الصفحات المراد ترقيمها، الافتراضي 'الكل'، يقبل أيضًا 1-5 أو 2,5,9 إلخ -addPageNumbers.customNumberDesc=الافتراضي هو {n}، يقبل أيضًا 'الصفحة {n} من {total}'، 'نص-{n}'، '{filename}-{n} +addPageNumbers.customNumberDesc=الافتراضي هو {n}، يقبل أيضًا 'الصفحة {n} من {total}'، 'نص-{n}'، '{filename}-{n}' addPageNumbers.submit=إضافة أرقام الصفحات diff --git a/app/core/src/main/resources/messages_az_AZ.properties b/app/core/src/main/resources/messages_az_AZ.properties index f0e3f5ea9..9f9e0122d 100644 --- a/app/core/src/main/resources/messages_az_AZ.properties +++ b/app/core/src/main/resources/messages_az_AZ.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Səhifələrə nömrə əlavə edin addPageNumbers.selectText.6=Fərdi Mətn addPageNumbers.customTextDesc=Fərdi Mətn addPageNumbers.numberPagesDesc=Hansı səhifələrin nömrələnəcəyini seçin, default 'all', və ya 1-5, 2,5,9 kimi yazılış qəbul olunur -addPageNumbers.customNumberDesc=Defolt olaraq {n}, və ya 'Page {n} of {total}', 'Text-{n}', '{filename}-{n} +addPageNumbers.customNumberDesc=Defolt olaraq {n}, və ya 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}' addPageNumbers.submit=Səhifə Nömrələri əlavə edin diff --git a/app/core/src/main/resources/messages_bg_BG.properties b/app/core/src/main/resources/messages_bg_BG.properties index d7964e792..f1004817c 100644 --- a/app/core/src/main/resources/messages_bg_BG.properties +++ b/app/core/src/main/resources/messages_bg_BG.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Страници към номер addPageNumbers.selectText.6=Персонализиран текст addPageNumbers.customTextDesc=Персонализиран текст addPageNumbers.numberPagesDesc=Кои страници да номерирате, по подразбиране 'всички', също приема 1-5 или 2,5,9 и т.н. -addPageNumbers.customNumberDesc=По подразбиране е {n}, също приема 'Страница {n} от {total}', 'Текст-{n}', '{filename}-{n} +addPageNumbers.customNumberDesc=По подразбиране е {n}, също приема 'Страница {n} от {total}', 'Текст-{n}', '{filename}-{n}' addPageNumbers.submit=Добавяне на номера на страници diff --git a/app/core/src/main/resources/messages_da_DK.properties b/app/core/src/main/resources/messages_da_DK.properties index b82f1d761..e67fae325 100644 --- a/app/core/src/main/resources/messages_da_DK.properties +++ b/app/core/src/main/resources/messages_da_DK.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Sider at nummerere addPageNumbers.selectText.6=Brugerdefineret Tekst addPageNumbers.customTextDesc=Brugerdefineret Tekst addPageNumbers.numberPagesDesc=Hvilke sider der skal nummereres, standard 'alle', accepterer også 1-5 eller 2,5,9 osv. -addPageNumbers.customNumberDesc=Standard er {n}, accepterer også 'Side {n} af {total}', 'Tekst-{n}', '{filnavn}-{n} +addPageNumbers.customNumberDesc=Standard er {n}, accepterer også 'Side {n} af {total}', 'Tekst-{n}', '{filename}-{n}' addPageNumbers.submit=Tilføj Sidenumre diff --git a/app/core/src/main/resources/messages_el_GR.properties b/app/core/src/main/resources/messages_el_GR.properties index 7f59f217e..3e52435f3 100644 --- a/app/core/src/main/resources/messages_el_GR.properties +++ b/app/core/src/main/resources/messages_el_GR.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Σελίδες προς αρίθμηση addPageNumbers.selectText.6=Προσαρμοσμένο κείμενο addPageNumbers.customTextDesc=Προσαρμοσμένο κείμενο addPageNumbers.numberPagesDesc=Ποιες σελίδες να αριθμηθούν, προεπιλογή 'all', δέχεται επίσης 1-5 ή 2,5,9 κλπ -addPageNumbers.customNumberDesc=Προεπιλογή σε {n}, δέχεται επίσης 'Σελίδα {n} από {total}', 'Κείμενο-{n}', '{filename}-{n} +addPageNumbers.customNumberDesc=Προεπιλογή σε {n}, δέχεται επίσης 'Σελίδα {n} από {total}', 'Κείμενο-{n}', '{filename}-{n}' addPageNumbers.submit=Προσθήκη αριθμών σελίδων diff --git a/app/core/src/main/resources/messages_en_GB.properties b/app/core/src/main/resources/messages_en_GB.properties index 599dd0989..3414a33f2 100644 --- a/app/core/src/main/resources/messages_en_GB.properties +++ b/app/core/src/main/resources/messages_en_GB.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Pages to Number addPageNumbers.selectText.6=Custom Text addPageNumbers.customTextDesc=Custom Text addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc -addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n} +addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}' addPageNumbers.submit=Add Page Numbers diff --git a/app/core/src/main/resources/messages_en_US.properties b/app/core/src/main/resources/messages_en_US.properties index 8ccbd7c99..66a09ee5b 100644 --- a/app/core/src/main/resources/messages_en_US.properties +++ b/app/core/src/main/resources/messages_en_US.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Pages to Number addPageNumbers.selectText.6=Custom Text addPageNumbers.customTextDesc=Custom Text addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc -addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n} +addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}' addPageNumbers.submit=Add Page Numbers diff --git a/app/core/src/main/resources/messages_es_ES.properties b/app/core/src/main/resources/messages_es_ES.properties index ae63d5107..3615d981c 100644 --- a/app/core/src/main/resources/messages_es_ES.properties +++ b/app/core/src/main/resources/messages_es_ES.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Páginas a numerar addPageNumbers.selectText.6=Texto personalizado addPageNumbers.customTextDesc=Texto personalizado addPageNumbers.numberPagesDesc=Qué páginas numerar, por defecto 'todas', también acepta 1-5 o 2,5,9 etc -addPageNumbers.customNumberDesc=Por defecto a {n}, también acepta 'Página {n} de {total}', 'Texto-{n}', '{filename}-{n} +addPageNumbers.customNumberDesc=Por defecto a {n}, también acepta 'Página {n} de {total}', 'Texto-{n}', '{filename}-{n}' addPageNumbers.submit=Añadir Números de Página diff --git a/app/core/src/main/resources/messages_ga_IE.properties b/app/core/src/main/resources/messages_ga_IE.properties index b0363acb4..c7075c413 100644 --- a/app/core/src/main/resources/messages_ga_IE.properties +++ b/app/core/src/main/resources/messages_ga_IE.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Leathanaigh go hUimhir addPageNumbers.selectText.6=Téacs Saincheaptha addPageNumbers.customTextDesc=Téacs Saincheaptha addPageNumbers.numberPagesDesc=Cé na leathanaigh le huimhriú, réamhshocraithe 'gach duine', a ghlacann freisin 1-5 nó 2,5,9 etc -addPageNumbers.customNumberDesc=Réamhshocrú go {n}, glacann sé freisin le 'Leathanach {n} de {total}', 'Text-{n}', '{filename}-{n} +addPageNumbers.customNumberDesc=Réamhshocrú go {n}, glacann sé freisin le 'Leathanach {n} de {total}', 'Text-{n}', '{filename}-{n}' addPageNumbers.submit=Cuir Uimhreacha Leathanaigh leis diff --git a/app/core/src/main/resources/messages_hr_HR.properties b/app/core/src/main/resources/messages_hr_HR.properties index cb06aba43..dd83f3c43 100644 --- a/app/core/src/main/resources/messages_hr_HR.properties +++ b/app/core/src/main/resources/messages_hr_HR.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Brojanje stranica addPageNumbers.selectText.6=Prilagođeni tekst addPageNumbers.customTextDesc=Prilagođeni tekst addPageNumbers.numberPagesDesc=Koje stranice numerirati, zadano je 'sve', također prihvaća 1-5 ili 2,5,9 itd. -addPageNumbers.customNumberDesc=Zadano je {n}, također prihvaća 'Stranica {n} od {total}', 'Tekst-{n}', '{ime datoteke}-{n}' +addPageNumbers.customNumberDesc=Zadano je {n}, također prihvaća 'Stranica {n} od {total}', 'Tekst-{n}', '{filename}-{n}' addPageNumbers.submit=Dodaj brojeve stranica diff --git a/app/core/src/main/resources/messages_id_ID.properties b/app/core/src/main/resources/messages_id_ID.properties index d06da87ab..70f874971 100644 --- a/app/core/src/main/resources/messages_id_ID.properties +++ b/app/core/src/main/resources/messages_id_ID.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Halaman ke Nomor addPageNumbers.selectText.6=Teks Khusus addPageNumbers.customTextDesc=Teks Khusus addPageNumbers.numberPagesDesc=Halaman mana yang akan diberi nomor, default 'semua', juga menerima 1-5 atau 2,5,9, dll. -addPageNumbers.customNumberDesc=Default untuk {n}, juga menerima 'Halaman {n} dari {total}', 'Teks-{n}', '{nama berkas}-{n}' +addPageNumbers.customNumberDesc=Default untuk {n}, juga menerima 'Halaman {n} dari {total}', 'Teks-{n}', '{filename}-{n}' addPageNumbers.submit=Tambahkan Nomor Halaman diff --git a/app/core/src/main/resources/messages_it_IT.properties b/app/core/src/main/resources/messages_it_IT.properties index 7491624f0..29c3acf86 100644 --- a/app/core/src/main/resources/messages_it_IT.properties +++ b/app/core/src/main/resources/messages_it_IT.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Pagine da numerare addPageNumbers.selectText.6=Testo personalizzato addPageNumbers.customTextDesc=Testo personalizzato addPageNumbers.numberPagesDesc=Quali pagine numerare, impostazione predefinita "tutte", accetta anche 1-5 o 2,5,9 ecc -addPageNumbers.customNumberDesc=Il valore predefinito è {n}, accetta anche 'Pagina {n} di {total}', 'Testo-{n}', '{filename}-{n} +addPageNumbers.customNumberDesc=Il valore predefinito è {n}, accetta anche 'Pagina {n} di {total}', 'Testo-{n}', '{filename}-{n}' addPageNumbers.submit=Aggiungi numeri di pagina diff --git a/app/core/src/main/resources/messages_nl_NL.properties b/app/core/src/main/resources/messages_nl_NL.properties index f7aa1e805..04e26701e 100644 --- a/app/core/src/main/resources/messages_nl_NL.properties +++ b/app/core/src/main/resources/messages_nl_NL.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Pagina's om te nummeren addPageNumbers.selectText.6=Aangepaste tekst addPageNumbers.customTextDesc=Aangepaste tekst addPageNumbers.numberPagesDesc=Welke pagina's genummerd moeten worden, standaard 'all', accepteert ook 1-5 of 2,5,9 etc -addPageNumbers.customNumberDesc=Standaard {n}, accepteert ook 'Pagina {n} van {total}', 'Tekst-{n}', '{filename}-{n} +addPageNumbers.customNumberDesc=Standaard {n}, accepteert ook 'Pagina {n} van {total}', 'Tekst-{n}', '{filename}-{n}' addPageNumbers.submit=Paginanummers toevoegen diff --git a/app/core/src/main/resources/messages_no_NB.properties b/app/core/src/main/resources/messages_no_NB.properties index ae9091cf5..21f25e613 100644 --- a/app/core/src/main/resources/messages_no_NB.properties +++ b/app/core/src/main/resources/messages_no_NB.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Sider å nummerere addPageNumbers.selectText.6=Tilpasset Tekst addPageNumbers.customTextDesc=Tilpasset Tekst addPageNumbers.numberPagesDesc=Hvilke sider som skal nummereres, standard 'alle', aksepterer også 1-5 eller 2,5,9 osv. -addPageNumbers.customNumberDesc=Standard til {n}, aksepterer også 'Side {n} av {total}', 'Tekst-{n}', '{filnavn}-{n} +addPageNumbers.customNumberDesc=Standard til {n}, aksepterer også 'Side {n} av {total}', 'Tekst-{n}', '{filename}-{n}' addPageNumbers.submit=Legg til Sidetall diff --git a/app/core/src/main/resources/messages_pl_PL.properties b/app/core/src/main/resources/messages_pl_PL.properties index 9c5dc670e..5b5ec5291 100644 --- a/app/core/src/main/resources/messages_pl_PL.properties +++ b/app/core/src/main/resources/messages_pl_PL.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Ilość stron do ponumerowania addPageNumbers.selectText.6=Tekst własny addPageNumbers.customTextDesc=Tekst własny addPageNumbers.numberPagesDesc=Strony do numeracji, wszystkie (all), 1-5, 2, 5, 9 -addPageNumbers.customNumberDesc=Domyślnie do {n}, również akceptuje 'Strona {n} z {total},Teskt-{n},'{filename}-{n} +addPageNumbers.customNumberDesc=Domyślnie do {n}, również akceptuje 'Strona {n} z {total}', 'Tekst-{n}', '{filename}-{n}' addPageNumbers.submit=Dodaj numerację stron diff --git a/app/core/src/main/resources/messages_pt_BR.properties b/app/core/src/main/resources/messages_pt_BR.properties index bf2cb6a17..045aaa8bb 100644 --- a/app/core/src/main/resources/messages_pt_BR.properties +++ b/app/core/src/main/resources/messages_pt_BR.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Páginas a numerar: addPageNumbers.selectText.6=Texto personalizado: addPageNumbers.customTextDesc=Texto personalizado: addPageNumbers.numberPagesDesc=Quais páginas numerar, padrão 'todas', também aceita 1-5 ou 2,5,9,etc. -addPageNumbers.customNumberDesc=O padrão é {n}, também aceita 'Página {n} de {total}', 'Texto-{n}', '{nome do arquivo}-{n}' +addPageNumbers.customNumberDesc=O padrão é {n}, também aceita 'Página {n} de {total}', 'Texto-{n}', '{filename}-{n}' addPageNumbers.submit=Adicionar Números de Página diff --git a/app/core/src/main/resources/messages_pt_PT.properties b/app/core/src/main/resources/messages_pt_PT.properties index 7b73092f1..9a2449183 100644 --- a/app/core/src/main/resources/messages_pt_PT.properties +++ b/app/core/src/main/resources/messages_pt_PT.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Páginas a Numerar addPageNumbers.selectText.6=Texto Personalizado addPageNumbers.customTextDesc=Texto Personalizado addPageNumbers.numberPagesDesc=Quais páginas a numerar, predefinição 'todas', também aceita 1-5 ou 2,5,9 etc -addPageNumbers.customNumberDesc=Predefinição {n}, também aceita 'Página {n} de {total}', 'Texto-{n}', '{filename}-{n} +addPageNumbers.customNumberDesc=Predefinição {n}, também aceita 'Página {n} de {total}', 'Texto-{n}', '{filename}-{n}' addPageNumbers.submit=Adicionar Números de Página diff --git a/app/core/src/main/resources/messages_ro_RO.properties b/app/core/src/main/resources/messages_ro_RO.properties index 07fee9b86..a6270c69e 100644 --- a/app/core/src/main/resources/messages_ro_RO.properties +++ b/app/core/src/main/resources/messages_ro_RO.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Pagini de Numerotat addPageNumbers.selectText.6=Text Personalizat addPageNumbers.customTextDesc=Text Personalizat addPageNumbers.numberPagesDesc=Ce pagini să numeroteze, implicit 'toate', acceptă și 1-5 sau 2,5,9 etc -addPageNumbers.customNumberDesc=Implicit la {n}, acceptă și 'Pagina {n} din {total}', 'Text-{n}', '{nume_fisier}-{n} +addPageNumbers.customNumberDesc=Implicit la {n}, acceptă și 'Pagina {n} din {total}', 'Text-{n}', '{filename}-{n}' addPageNumbers.submit=Adaugă Numere de Pagină diff --git a/app/core/src/main/resources/messages_sk_SK.properties b/app/core/src/main/resources/messages_sk_SK.properties index 4b84511f5..60fa06ad3 100644 --- a/app/core/src/main/resources/messages_sk_SK.properties +++ b/app/core/src/main/resources/messages_sk_SK.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Stránky na číslovanie addPageNumbers.selectText.6=Vlastný text addPageNumbers.customTextDesc=Vlastný text addPageNumbers.numberPagesDesc=Ktoré stránky číslovať, predvolené 'všetky', tiež akceptuje 1-5 alebo 2,5,9 atď. -addPageNumbers.customNumberDesc=Predvolené {n}, tiež akceptuje 'Strana {n} z {total}', 'Text-{n}', '{filename}-{n} +addPageNumbers.customNumberDesc=Predvolené {n}, tiež akceptuje 'Strana {n} z {total}', 'Text-{n}', '{filename}-{n}' addPageNumbers.submit=Pridať čísla stránok diff --git a/app/core/src/main/resources/messages_sl_SI.properties b/app/core/src/main/resources/messages_sl_SI.properties index 72987dfcd..820d27a98 100644 --- a/app/core/src/main/resources/messages_sl_SI.properties +++ b/app/core/src/main/resources/messages_sl_SI.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Strani v številko addPageNumbers.selectText.6=Besedilo po meri addPageNumbers.customTextDesc=Besedilo po meri addPageNumbers.numberPagesDesc=Katere strani oštevilčiti, privzeto 'vse', sprejema tudi 1-5 ali 2,5,9 itd. -addPageNumbers.customNumberDesc=Privzeto na {n}, sprejema tudi 'Stran {n} od {total}', 'Besedilo-{n}', '{filename}-{n} +addPageNumbers.customNumberDesc=Privzeto na {n}, sprejema tudi 'Stran {n} od {total}', 'Besedilo-{n}', '{filename}-{n}' addPageNumbers.submit=Dodaj številke strani diff --git a/app/core/src/main/resources/messages_sr_LATN_RS.properties b/app/core/src/main/resources/messages_sr_LATN_RS.properties index 4a6e987ca..9c578910c 100644 --- a/app/core/src/main/resources/messages_sr_LATN_RS.properties +++ b/app/core/src/main/resources/messages_sr_LATN_RS.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Stranice za numerisanje: addPageNumbers.selectText.6=Prilagođeni tekst: addPageNumbers.customTextDesc=Prilagođeni tekst addPageNumbers.numberPagesDesc=Koje stranice brojati, podrazumevano 'sve', takođe prihvata 1-5 ili 2,5,9 itd. -addPageNumbers.customNumberDesc=Podrazumevano je {n}, takođe prihvata 'Stranica {n} od {ukupno}', 'Tekst-{n}', '{ime_fajla}-{n}' +addPageNumbers.customNumberDesc=Podrazumevano je {n}, takođe prihvata 'Stranica {n} od {total}', 'Tekst-{n}', '{filename}-{n}' addPageNumbers.submit=Numeriši diff --git a/app/core/src/main/resources/messages_sv_SE.properties b/app/core/src/main/resources/messages_sv_SE.properties index 0182c8f98..d8ae5299b 100644 --- a/app/core/src/main/resources/messages_sv_SE.properties +++ b/app/core/src/main/resources/messages_sv_SE.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Sidor att numrera addPageNumbers.selectText.6=Anpassad text addPageNumbers.customTextDesc=Anpassad text addPageNumbers.numberPagesDesc=Vilka sidor som ska numreras, standard 'all', accepterar även 1-5 eller 2,5,9 etc -addPageNumbers.customNumberDesc=Standard är {n}, accepterar även 'Sida {n} av {total}', 'Text-{n}', '{filnamn}-{n} +addPageNumbers.customNumberDesc=Standard är {n}, accepterar även 'Sida {n} av {total}', 'Text-{n}', '{filename}-{n}' addPageNumbers.submit=Lägg till sidnummer diff --git a/app/core/src/main/resources/messages_tr_TR.properties b/app/core/src/main/resources/messages_tr_TR.properties index ca084cc0d..b9d1f9439 100644 --- a/app/core/src/main/resources/messages_tr_TR.properties +++ b/app/core/src/main/resources/messages_tr_TR.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Numaralandırılacak Sayfalar addPageNumbers.selectText.6=Özel Metin addPageNumbers.customTextDesc=Özel Metin addPageNumbers.numberPagesDesc=Hangi sayfaların numaralandırılacağını, varsayılan 'all', ayrıca 1-5 veya 2,5,9 vb. kabul eder -addPageNumbers.customNumberDesc=Varsayılan {n}, ayrıca 'Sayfa {n} / {total}', 'Metin-{n}', '{filename}-{n} kabul eder +addPageNumbers.customNumberDesc=Varsayılan {n}, ayrıca 'Sayfa {n} / {total}', 'Metin-{n}', '{filename}-{n}' kabul eder addPageNumbers.submit=Sayfa Numaraları Ekle diff --git a/app/core/src/main/resources/messages_vi_VN.properties b/app/core/src/main/resources/messages_vi_VN.properties index ba7ba416b..1a72402a5 100644 --- a/app/core/src/main/resources/messages_vi_VN.properties +++ b/app/core/src/main/resources/messages_vi_VN.properties @@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Trang cần đánh số addPageNumbers.selectText.6=Văn bản tùy chỉnh addPageNumbers.customTextDesc=Văn bản tùy chỉnh addPageNumbers.numberPagesDesc=Những trang cần đánh số, mặc định là 'all', cũng chấp nhận 1-5 hoặc 2,5,9 v.v. -addPageNumbers.customNumberDesc=Mặc định là {n}, cũng chấp nhận 'Trang {n} / {total}', 'Văn bản-{n}', '{filename}-{n} +addPageNumbers.customNumberDesc=Mặc định là {n}, cũng chấp nhận 'Trang {n} / {total}', 'Văn bản-{n}', '{filename}-{n}' addPageNumbers.submit=Thêm số trang From d9a1ed6df15c8f9e00fadc67b0e20a90eec281af Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Mon, 25 Aug 2025 04:41:09 +0800 Subject: [PATCH 13/20] Update and improve the zh-TW Traditional Chinese translation (#4164) # Description of Changes Update and improve the zh-TW Traditional Chinese translation Summary from GitHub Copilot: > This pull request updates the Traditional Chinese localization file `messages_zh_TW.properties` to improve translation accuracy and consistency across the UI. The changes focus on refining update-related strings, clarifying PDF tool descriptions, and localizing bookmark editor actions. > > **Update & Upgrade Experience:** > * Improved wording and localization for update notifications, modal dialogs, update priority levels, and migration guide references to better match native language conventions. > * Refined enterprise edition feature descriptions, specifically clarifying SSO login language for professional features. > * Updated SSO login string for the login screen to use more natural phrasing. > > **PDF Tool Descriptions:** > * Enhanced descriptions for "Auto Split PDF" and "PDF to Single Page/Image" tools, making instructions clearer and terminology more consistent. [[1]](diffhunk://#diff-b982180d8edd5c66aef4cfc826fe1cabeb4d22644359f2f749ce0bec4760b40aL809-R809) [[2]](diffhunk://#diff-b982180d8edd5c66aef4cfc826fe1cabeb4d22644359f2f749ce0bec4760b40aL1111-R1118) [[3]](diffhunk://#diff-b982180d8edd5c66aef4cfc826fe1cabeb4d22644359f2f749ce0bec4760b40aL1431-R1431) > > **Bookmark Editor Localization:** > * Fully localized import/export actions and descriptions in the bookmark editor, replacing English terms with accurate Chinese equivalents. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [x] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --- .../main/resources/messages_zh_TW.properties | 92 +++++++++---------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/app/core/src/main/resources/messages_zh_TW.properties b/app/core/src/main/resources/messages_zh_TW.properties index c2cf4518c..1142aea04 100644 --- a/app/core/src/main/resources/messages_zh_TW.properties +++ b/app/core/src/main/resources/messages_zh_TW.properties @@ -325,7 +325,7 @@ pipelineOptions.validateButton=驗證 ######################## enterpriseEdition.button=升級至專業版 enterpriseEdition.warning=此功能僅提供給專業版使用者使用。 -enterpriseEdition.yamlAdvert=Stirling PDF 專業版支援 YAML 設定檔和其他單一登入 (SSO) 功能。 +enterpriseEdition.yamlAdvert=Stirling PDF 專業版支援 YAML 設定檔和其他 SSO 登入功能。 enterpriseEdition.ssoAdvert=需要更多使用者管理功能嗎?請參考 Stirling PDF 專業版 enterpriseEdition.proTeamFeatureDisabled=團隊管理功能需要專業版或更進階的授權 @@ -364,42 +364,42 @@ navbar.sections.popular=熱門功能 # SETTINGS # ############# settings.title=設定 -settings.update=有更新可用 -settings.updateAvailable=目前安裝的版本是 {0}。有新版本({1})可供使用。 +settings.update=有可用更新 +settings.updateAvailable=目前安裝的版本為 {0},已有新版本({1})可供更新。 # Update modal and notification strings -update.urgentUpdateAvailable=🚨 Update Available -update.updateAvailable=Update Available -update.modalTitle=Update Available -update.current=Current -update.latest=Latest -update.latestStable=Latest Stable -update.priority=Priority -update.recommendedAction=Recommended Action -update.breakingChangesDetected=⚠️ Breaking Changes Detected -update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below. -update.migrationGuides=Migration Guides: -update.viewGuide=View Guide -update.loadingDetailedInfo=Loading detailed version information... -update.close=Close -update.viewAllReleases=View All Releases -update.downloadLatest=Download Latest -update.availableUpdates=Available Updates: -update.unableToLoadDetails=Unable to load detailed version information. -update.version=Version +update.urgentUpdateAvailable=🚨 有緊急更新 +update.updateAvailable=有可用更新 +update.modalTitle=有可用更新 +update.current=目前版本 +update.latest=最新版本 +update.latestStable=最新穩定版本 +update.priority=優先等級 +update.recommendedAction=建議操作 +update.breakingChangesDetected=⚠️ 偵測到重大變更 +update.breakingChangesMessage=此更新包含可能無法向下相容的重大變更,請先參閱下方的遷移指南。 +update.migrationGuides=遷移指南: +update.viewGuide=檢視指南 +update.loadingDetailedInfo=正在載入版本詳細資訊... +update.close=關閉 +update.viewAllReleases=檢視所有版本 +update.downloadLatest=下載最新版 +update.availableUpdates=可用更新: +update.unableToLoadDetails=無法載入版本詳細資訊。 +update.version=版本 # Update priority levels -update.priority.urgent=URGENT -update.priority.normal=NORMAL -update.priority.minor=MINOR -update.priority.low=LOW +update.priority.urgent=緊急更新 +update.priority.normal=一般更新 +update.priority.minor=次要更新 +update.priority.low=低優先度更新 # Breaking changes text -update.breakingChanges=Breaking Changes: -update.breakingChangesDefault=This version contains breaking changes -update.migrationGuide=Migration Guide +update.breakingChanges=重大變更: +update.breakingChangesDefault=此版本包含可能無法向下相容的重大變更 +update.migrationGuide=遷移指南 settings.appVersion=應用程式版本: -settings.downloadOption.title=選擇下載選項(適用於單一檔案非壓縮下載): +settings.downloadOption.title=選擇下載選項(適用於單檔無壓縮下載): settings.downloadOption.1=在同一視窗中開啟 settings.downloadOption.2=在新視窗中開啟 settings.downloadOption.3=下載檔案 @@ -806,7 +806,7 @@ home.extractPage.desc=從 PDF 中提取選定的頁面 extractPage.tags=提取 -home.PdfToSinglePage.title=PDF 轉單一大頁面 +home.PdfToSinglePage.title=PDF 轉單一大型頁面 home.PdfToSinglePage.desc=將所有 PDF 頁面合併為一個大的單一頁面 PdfToSinglePage.tags=單一頁面 @@ -893,7 +893,7 @@ login.rememberme=記住我 login.invalid=使用者名稱或密碼無效。 login.locked=您的帳號已被鎖定。 login.signinTitle=請登入 -login.ssoSignIn=透過 SSO 單一登入 +login.ssoSignIn=透過 SSO 登入 login.oAuth2AutoCreateDisabled=OAuth 2.0 自動建立使用者功能已停用 login.oAuth2AdminBlockedUser=目前不允許未註冊的使用者註冊或登入。請聯絡系統管理員。 login.oauth2RequestNotFound=找不到驗證請求 @@ -1109,14 +1109,14 @@ crop.submit=送出 #autoSplitPDF autoSplitPDF.title=自動分割 PDF autoSplitPDF.header=自動分割 PDF -autoSplitPDF.description=列印,插入,掃描,上傳,讓 Stirling PDF 處理其餘的工作。不需要手動工作排序。 -autoSplitPDF.selectText.1=從下面列印一些分隔紙張(黑白即可)。 -autoSplitPDF.selectText.2=透過在它們之間插入分隔紙張一次掃描所有文件。 -autoSplitPDF.selectText.3=上傳單一大的掃描 PDF 檔案,讓 Stirling PDF 處理其餘的工作。 -autoSplitPDF.selectText.4=自動偵測並移除分隔頁面,確保最終文件整潔。 -autoSplitPDF.formPrompt=送出包含 Stirling-PDF 頁面分隔器的 PDF: +autoSplitPDF.description=列印、插入、掃描、上傳,剩下的就交給 Stirling PDF 自動處理,無需手動排序。 +autoSplitPDF.selectText.1=從下方列印分隔頁(黑白列印即可)。 +autoSplitPDF.selectText.2=將分隔頁夾在文件之間,一次掃描全部文件。 +autoSplitPDF.selectText.3=上傳完整的單一掃描 PDF 檔,剩下的交給 Stirling PDF 自動處理。 +autoSplitPDF.selectText.4=系統會自動偵測並移除分隔頁,確保輸出的文件整齊乾淨。 +autoSplitPDF.formPrompt=送出包含 Stirling PDF 分隔頁的 PDF 檔案: autoSplitPDF.duplexMode=雙面模式(正反面掃描) -autoSplitPDF.dividerDownload2=下載 '自動分割器分隔器(帶說明).pdf' +autoSplitPDF.dividerDownload2=下載《自動分割用分隔頁(含使用說明).pdf》 autoSplitPDF.submit=送出 @@ -1429,7 +1429,7 @@ pdfToImage.title=PDF 轉圖片 pdfToImage.header=PDF 轉圖片 pdfToImage.selectText=影像格式 pdfToImage.singleOrMultiple=頁面到影像的結果類型 -pdfToImage.single=單一大影像結合所有頁面 +pdfToImage.single=單一大型影像結合所有頁面 pdfToImage.multi=多個影像,每頁一個影像 pdfToImage.colorType=顏色類型 pdfToImage.color=顏色 @@ -1893,12 +1893,12 @@ editTableOfContents.replaceExisting=取代現有書籤 (取消勾選以附加到 editTableOfContents.editorTitle=書籤編輯器 editTableOfContents.editorDesc=在下方新增和排列書籤。點選 + 新增子書籤。 editTableOfContents.addBookmark=新增書籤 -editTableOfContents.importBookmarksDefault=Import -editTableOfContents.importBookmarksFromJsonFile=Upload JSON file -editTableOfContents.importBookmarksFromClipboard=Paste from clipboard -editTableOfContents.exportBookmarksDefault=Export -editTableOfContents.exportBookmarksAsJson=Download as JSON -editTableOfContents.exportBookmarksAsText=Copy as text +editTableOfContents.importBookmarksDefault=匯入 +editTableOfContents.importBookmarksFromJsonFile=上傳 JSON 檔案 +editTableOfContents.importBookmarksFromClipboard=從剪貼簿貼上 +editTableOfContents.exportBookmarksDefault=匯出 +editTableOfContents.exportBookmarksAsJson=下載為 JSON +editTableOfContents.exportBookmarksAsText=複製為文字 editTableOfContents.desc.1=此工具可讓您在 PDF 文件中新增或編輯目錄 (書籤)。 editTableOfContents.desc.2=您可以透過將子書籤新增至父書籤來建立階層式結構。 editTableOfContents.desc.3=每個書籤都需要標題和目標頁碼。 From 930fcf01bfcd8d46b2d2a5cbb1a839ca6569000e Mon Sep 17 00:00:00 2001 From: Ludy Date: Sun, 24 Aug 2025 22:42:31 +0200 Subject: [PATCH 14/20] fix(downloader): reset progress bar after completion (#4192) # Description of Changes - Added a `setTimeout` to hide the `.progressBarContainer` 1 second after reaching 100%. - Reset progress bar width to `0%` and `aria-valuenow` to `0` to prepare for future downloads. - This change ensures the UI does not leave a full progress bar displayed after a completed download. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- app/core/src/main/resources/static/js/downloader.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/core/src/main/resources/static/js/downloader.js b/app/core/src/main/resources/static/js/downloader.js index b5324dd82..47e1b06af 100644 --- a/app/core/src/main/resources/static/js/downloader.js +++ b/app/core/src/main/resources/static/js/downloader.js @@ -482,6 +482,11 @@ } progressBar.css('width', '100%'); progressBar.attr('aria-valuenow', Array.from(files).length); + setTimeout(() => { + progressBar.closest('.progressBarContainer').hide(); + progressBar.css('width', '0%'); + progressBar.attr('aria-valuenow', 0); + }, 1000); } function updateProgressBar(progressBar, files) { From 97132c28a403d567fc82b909cfdaa76012d81303 Mon Sep 17 00:00:00 2001 From: Ludy Date: Sun, 24 Aug 2025 22:44:50 +0200 Subject: [PATCH 15/20] build(gradle): include all subprojects in license report generation (#4170) # Description of Changes - Updated `build.gradle` to ensure the `licenseReport` task processes both the root project and all subprojects. - Introduced `allProjects` variable that merges `subprojects` with the current project into a single set. - This change ensures license reporting covers the full multi-module project rather than only the root module. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --- .github/workflows/build.yml | 7 +++++-- .github/workflows/licenses-update.yml | 3 +++ build.gradle | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 60085f9c9..19108b186 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -175,10 +175,13 @@ jobs: java-version: "17" distribution: "temurin" - - name: check the licenses for compatibility + - name: Check licenses for compatibility run: ./gradlew clean checkLicense + env: + DISABLE_ADDITIONAL_FEATURES: false + STIRLING_PDF_DESKTOP_UI: true - - name: FAILED - check the licenses for compatibility + - name: FAILED - Check licenses for compatibility if: failure() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: diff --git a/.github/workflows/licenses-update.yml b/.github/workflows/licenses-update.yml index 1f920e2da..5971f7dc9 100644 --- a/.github/workflows/licenses-update.yml +++ b/.github/workflows/licenses-update.yml @@ -58,6 +58,9 @@ jobs: - name: Check licenses for compatibility run: ./gradlew clean checkLicense + env: + DISABLE_ADDITIONAL_FEATURES: false + STIRLING_PDF_DESKTOP_UI: true - name: Upload artifact on failure if: failure() diff --git a/build.gradle b/build.gradle index 4016c2bc6..b722517a2 100644 --- a/build.gradle +++ b/build.gradle @@ -222,11 +222,14 @@ tasks.withType(JavaCompile).configureEach { dependsOn "spotlessApply" } +def allProjects = ((subprojects as Set) + project) as Set + licenseReport { - projects = [project] + projects = allProjects renderers = [new JsonReportRenderer()] allowedLicensesFile = project.layout.projectDirectory.file("app/allowed-licenses.json").asFile outputDir = project.layout.buildDirectory.dir("reports/dependency-license").get().asFile.path + configurations = [ "productionRuntimeClasspath", "runtimeClasspath" ] } // Configure the forked spring boot run task to properly delegate to the stirling-pdf module From f0cfd87a5a71de919d47733f11aa7baee59810d3 Mon Sep 17 00:00:00 2001 From: Ludy Date: Sun, 24 Aug 2025 22:45:26 +0200 Subject: [PATCH 16/20] build(gradle): replace deprecated `outputFile` with `destinationFile` in `writeVersion` task (#4167) # Description of Changes - Replaced the deprecated `outputFile` property with `destinationFile` in the `writeVersion` Gradle task. - Updated the logging statement to use `destinationFile.get().asFile.path` instead of the old `outputFile.path`. - This change ensures compatibility with newer Gradle versions and removes deprecation warnings. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index b722517a2..5f182428f 100644 --- a/build.gradle +++ b/build.gradle @@ -74,8 +74,8 @@ allprojects { } tasks.register('writeVersion', WriteProperties) { - outputFile = layout.projectDirectory.file('app/common/src/main/resources/version.properties') - println "Writing version.properties to ${outputFile.path}" + destinationFile = layout.projectDirectory.file('app/common/src/main/resources/version.properties') + println "Writing version.properties to ${destinationFile.get().asFile.path}" comment = "${new Date()}" property 'version', project.provider { project.version.toString() } } From 73df0ae1a8fe0ea52f2c7d4345d239ed94e65293 Mon Sep 17 00:00:00 2001 From: Ludy Date: Sun, 24 Aug 2025 22:47:09 +0200 Subject: [PATCH 17/20] fix(config): recreate settings.yml if missing or below minimal size threshold (#4166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description of Changes - Added logic to verify the `settings.yml` file’s existence **and** ensure it has at least 31 lines (minimum valid config since `v0.13.0`). - If the file exists but is too small, it is moved to a timestamped `.bak` backup before creating a new one from the template. - Added logging to show current line count and backup location for better traceability. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --- .../configuration/ConfigInitializer.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/app/common/src/main/java/stirling/software/common/configuration/ConfigInitializer.java b/app/common/src/main/java/stirling/software/common/configuration/ConfigInitializer.java index 50090ee51..54e42504c 100644 --- a/app/common/src/main/java/stirling/software/common/configuration/ConfigInitializer.java +++ b/app/common/src/main/java/stirling/software/common/configuration/ConfigInitializer.java @@ -23,10 +23,30 @@ import stirling.software.common.util.YamlHelper; @Slf4j public class ConfigInitializer { + private static final int MIN_SETTINGS_FILE_LINES = 31; + public void ensureConfigExists() throws IOException, URISyntaxException { // 1) If settings file doesn't exist, create from template Path destPath = Paths.get(InstallationPathConfig.getSettingsPath()); - if (Files.notExists(destPath)) { + + boolean settingsFileExists = Files.exists(destPath); + + long lineCount = settingsFileExists ? Files.readAllLines(destPath).size() : 0; + + log.info("Current settings file line count: {}", lineCount); + + if (!settingsFileExists || lineCount < MIN_SETTINGS_FILE_LINES) { + if (settingsFileExists) { + // move settings.yml to settings.yml.{timestamp}.bak + Path backupPath = + Paths.get( + InstallationPathConfig.getSettingsPath() + + "." + + System.currentTimeMillis() + + ".bak"); + Files.move(destPath, backupPath, StandardCopyOption.REPLACE_EXISTING); + log.info("Moved existing settings file to backup: {}", backupPath); + } Files.createDirectories(destPath.getParent()); try (InputStream in = getClass().getClassLoader().getResourceAsStream("settings.yml.template")) { From f5f011f1e07c188ec26fc3443e7db30303202c31 Mon Sep 17 00:00:00 2001 From: Ludy Date: Sun, 24 Aug 2025 23:03:12 +0200 Subject: [PATCH 18/20] deps: Pin Python dev dependencies and lock hashes to remediate security alert 302 (#4173) ## Description of Changes - **What was changed** - Added `.github/scripts/requirements_dev.in` and an autogenerated, hash-locked `.github/scripts/requirements_dev.txt` to control Python dev dependencies via `pip-compile`. - **Why the change was made** - To remediate a GitHub code scanning alert by removing vulnerable transitive ranges and ensuring reproducible installs with vetted versions and hashes. - **Any challenges encountered** - Reconciling version constraints among image/PDF tooling (e.g., Pillow, pdf2image, OpenCV, WeasyPrint) while keeping wheels available across CI platforms. - Ensuring the generated lockfile remains maintainable and can be refreshed with `pip-compile` when needed. Closes #https://github.com/Stirling-Tools/Stirling-PDF/security/code-scanning/302 --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --- .github/scripts/requirements_dev.in | 8 + .github/scripts/requirements_dev.txt | 638 +++++++++++++++++++++++++++ Dockerfile.dev | 4 +- 3 files changed, 648 insertions(+), 2 deletions(-) create mode 100644 .github/scripts/requirements_dev.in create mode 100644 .github/scripts/requirements_dev.txt diff --git a/.github/scripts/requirements_dev.in b/.github/scripts/requirements_dev.in new file mode 100644 index 000000000..a8732d927 --- /dev/null +++ b/.github/scripts/requirements_dev.in @@ -0,0 +1,8 @@ +pip +setuptools +WeasyPrint +pdf2image +pillow +unoserver +opencv-python-headless +pre-commit diff --git a/.github/scripts/requirements_dev.txt b/.github/scripts/requirements_dev.txt new file mode 100644 index 000000000..be4e14a70 --- /dev/null +++ b/.github/scripts/requirements_dev.txt @@ -0,0 +1,638 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --generate-hashes --output-file='.github\scripts\requirements_dev.txt' --strip-extras '.github\scripts\requirements_dev.in' +# +brotli==1.1.0 \ + --hash=sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208 \ + --hash=sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48 \ + --hash=sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354 \ + --hash=sha256:0737ddb3068957cf1b054899b0883830bb1fec522ec76b1098f9b6e0f02d9419 \ + --hash=sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a \ + --hash=sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128 \ + --hash=sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c \ + --hash=sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088 \ + --hash=sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9 \ + --hash=sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a \ + --hash=sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3 \ + --hash=sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757 \ + --hash=sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2 \ + --hash=sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438 \ + --hash=sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578 \ + --hash=sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b \ + --hash=sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b \ + --hash=sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68 \ + --hash=sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0 \ + --hash=sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d \ + --hash=sha256:2feb1d960f760a575dbc5ab3b1c00504b24caaf6986e2dc2b01c09c87866a943 \ + --hash=sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd \ + --hash=sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409 \ + --hash=sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28 \ + --hash=sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da \ + --hash=sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50 \ + --hash=sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f \ + --hash=sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0 \ + --hash=sha256:3ee8a80d67a4334482d9712b8e83ca6b1d9bc7e351931252ebef5d8f7335a547 \ + --hash=sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180 \ + --hash=sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0 \ + --hash=sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d \ + --hash=sha256:4410f84b33374409552ac9b6903507cdb31cd30d2501fc5ca13d18f73548444a \ + --hash=sha256:494994f807ba0b92092a163a0a283961369a65f6cbe01e8891132b7a320e61eb \ + --hash=sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112 \ + --hash=sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc \ + --hash=sha256:4f3607b129417e111e30637af1b56f24f7a49e64763253bbc275c75fa887d4b2 \ + --hash=sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265 \ + --hash=sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327 \ + --hash=sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95 \ + --hash=sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec \ + --hash=sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd \ + --hash=sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c \ + --hash=sha256:5e55da2c8724191e5b557f8e18943b1b4839b8efc3ef60d65985bcf6f587dd38 \ + --hash=sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914 \ + --hash=sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0 \ + --hash=sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a \ + --hash=sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7 \ + --hash=sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368 \ + --hash=sha256:6974f52a02321b36847cd19d1b8e381bf39939c21efd6ee2fc13a28b0d99348c \ + --hash=sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0 \ + --hash=sha256:6c6e0c425f22c1c719c42670d561ad682f7bfeeef918edea971a79ac5252437f \ + --hash=sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451 \ + --hash=sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f \ + --hash=sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8 \ + --hash=sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e \ + --hash=sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248 \ + --hash=sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c \ + --hash=sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91 \ + --hash=sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724 \ + --hash=sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7 \ + --hash=sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966 \ + --hash=sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9 \ + --hash=sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97 \ + --hash=sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d \ + --hash=sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5 \ + --hash=sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf \ + --hash=sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac \ + --hash=sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b \ + --hash=sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951 \ + --hash=sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74 \ + --hash=sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648 \ + --hash=sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60 \ + --hash=sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c \ + --hash=sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1 \ + --hash=sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8 \ + --hash=sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d \ + --hash=sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc \ + --hash=sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61 \ + --hash=sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460 \ + --hash=sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751 \ + --hash=sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9 \ + --hash=sha256:a7e53012d2853a07a4a79c00643832161a910674a893d296c9f1259859a289d2 \ + --hash=sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0 \ + --hash=sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1 \ + --hash=sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474 \ + --hash=sha256:aea440a510e14e818e67bfc4027880e2fb500c2ccb20ab21c7a7c8b5b4703d75 \ + --hash=sha256:af6fa6817889314555aede9a919612b23739395ce767fe7fcbea9a80bf140fe5 \ + --hash=sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f \ + --hash=sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2 \ + --hash=sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f \ + --hash=sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb \ + --hash=sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6 \ + --hash=sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9 \ + --hash=sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111 \ + --hash=sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2 \ + --hash=sha256:cb1dac1770878ade83f2ccdf7d25e494f05c9165f5246b46a621cc849341dc01 \ + --hash=sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467 \ + --hash=sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619 \ + --hash=sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf \ + --hash=sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408 \ + --hash=sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579 \ + --hash=sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84 \ + --hash=sha256:d2b35ca2c7f81d173d2fadc2f4f31e88cc5f7a39ae5b6db5513cf3383b0e0ec7 \ + --hash=sha256:d342778ef319e1026af243ed0a07c97acf3bad33b9f29e7ae6a1f68fd083e90c \ + --hash=sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284 \ + --hash=sha256:d7702622a8b40c49bffb46e1e3ba2e81268d5c04a34f460978c6b5517a34dd52 \ + --hash=sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b \ + --hash=sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59 \ + --hash=sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752 \ + --hash=sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1 \ + --hash=sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80 \ + --hash=sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839 \ + --hash=sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0 \ + --hash=sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2 \ + --hash=sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3 \ + --hash=sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64 \ + --hash=sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089 \ + --hash=sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643 \ + --hash=sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b \ + --hash=sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e \ + --hash=sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985 \ + --hash=sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596 \ + --hash=sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2 \ + --hash=sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064 + # via fonttools +cffi==1.17.1 \ + --hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \ + --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \ + --hash=sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1 \ + --hash=sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15 \ + --hash=sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36 \ + --hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \ + --hash=sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8 \ + --hash=sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36 \ + --hash=sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17 \ + --hash=sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf \ + --hash=sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc \ + --hash=sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3 \ + --hash=sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed \ + --hash=sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702 \ + --hash=sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1 \ + --hash=sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8 \ + --hash=sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903 \ + --hash=sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6 \ + --hash=sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d \ + --hash=sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b \ + --hash=sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e \ + --hash=sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be \ + --hash=sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c \ + --hash=sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683 \ + --hash=sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9 \ + --hash=sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c \ + --hash=sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8 \ + --hash=sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1 \ + --hash=sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4 \ + --hash=sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655 \ + --hash=sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67 \ + --hash=sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595 \ + --hash=sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0 \ + --hash=sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65 \ + --hash=sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41 \ + --hash=sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6 \ + --hash=sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401 \ + --hash=sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6 \ + --hash=sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3 \ + --hash=sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16 \ + --hash=sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93 \ + --hash=sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e \ + --hash=sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4 \ + --hash=sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964 \ + --hash=sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c \ + --hash=sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576 \ + --hash=sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0 \ + --hash=sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3 \ + --hash=sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662 \ + --hash=sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3 \ + --hash=sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff \ + --hash=sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5 \ + --hash=sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd \ + --hash=sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f \ + --hash=sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5 \ + --hash=sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14 \ + --hash=sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d \ + --hash=sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9 \ + --hash=sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7 \ + --hash=sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382 \ + --hash=sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a \ + --hash=sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e \ + --hash=sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a \ + --hash=sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4 \ + --hash=sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99 \ + --hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \ + --hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b + # via weasyprint +cfgv==3.4.0 \ + --hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \ + --hash=sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560 + # via pre-commit +cssselect2==0.8.0 \ + --hash=sha256:46fc70ebc41ced7a32cd42d58b1884d72ade23d21e5a4eaaf022401c13f0e76e \ + --hash=sha256:7674ffb954a3b46162392aee2a3a0aedb2e14ecf99fcc28644900f4e6e3e9d3a + # via weasyprint +distlib==0.4.0 \ + --hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \ + --hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d + # via virtualenv +filelock==3.18.0 \ + --hash=sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2 \ + --hash=sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de + # via virtualenv +fonttools==4.59.0 \ + --hash=sha256:052444a5d0151878e87e3e512a1aa1a0ab35ee4c28afde0a778e23b0ace4a7de \ + --hash=sha256:169b99a2553a227f7b5fea8d9ecd673aa258617f466b2abc6091fe4512a0dcd0 \ + --hash=sha256:209b75943d158f610b78320eacb5539aa9e920bee2c775445b2846c65d20e19d \ + --hash=sha256:21e606b2d38fed938dde871c5736822dd6bda7a4631b92e509a1f5cd1b90c5df \ + --hash=sha256:241313683afd3baacb32a6bd124d0bce7404bc5280e12e291bae1b9bba28711d \ + --hash=sha256:26731739daa23b872643f0e4072d5939960237d540c35c14e6a06d47d71ca8fe \ + --hash=sha256:2e7cf8044ce2598bb87e44ba1d2c6e45d7a8decf56055b92906dc53f67c76d64 \ + --hash=sha256:31003b6a10f70742a63126b80863ab48175fb8272a18ca0846c0482968f0588e \ + --hash=sha256:332bfe685d1ac58ca8d62b8d6c71c2e52a6c64bc218dc8f7825c9ea51385aa01 \ + --hash=sha256:37c377f7cb2ab2eca8a0b319c68146d34a339792f9420fca6cd49cf28d370705 \ + --hash=sha256:37e01c6ec0c98599778c2e688350d624fa4770fbd6144551bd5e032f1199171c \ + --hash=sha256:401b1941ce37e78b8fd119b419b617277c65ae9417742a63282257434fd68ea2 \ + --hash=sha256:4536f2695fe5c1ffb528d84a35a7d3967e5558d2af58b4775e7ab1449d65767b \ + --hash=sha256:4c908a7036f0f3677f8afa577bcd973e3e20ddd2f7c42a33208d18bee95cdb6f \ + --hash=sha256:51ab1ff33c19e336c02dee1e9fd1abd974a4ca3d8f7eef2a104d0816a241ce97 \ + --hash=sha256:524133c1be38445c5c0575eacea42dbd44374b310b1ffc4b60ff01d881fabb96 \ + --hash=sha256:57bb7e26928573ee7c6504f54c05860d867fd35e675769f3ce01b52af38d48e2 \ + --hash=sha256:60f6665579e909b618282f3c14fa0b80570fbf1ee0e67678b9a9d43aa5d67a37 \ + --hash=sha256:62224a9bb85b4b66d1b46d45cbe43d71cbf8f527d332b177e3b96191ffbc1e64 \ + --hash=sha256:6770d7da00f358183d8fd5c4615436189e4f683bdb6affb02cad3d221d7bb757 \ + --hash=sha256:6801aeddb6acb2c42eafa45bc1cb98ba236871ae6f33f31e984670b749a8e58e \ + --hash=sha256:70d6b3ceaa9cc5a6ac52884f3b3d9544e8e231e95b23f138bdb78e6d4dc0eae3 \ + --hash=sha256:78813b49d749e1bb4db1c57f2d4d7e6db22c253cb0a86ad819f5dc197710d4b2 \ + --hash=sha256:841b2186adce48903c0fef235421ae21549020eca942c1da773ac380b056ab3c \ + --hash=sha256:84fc186980231a287b28560d3123bd255d3c6b6659828c642b4cf961e2b923d0 \ + --hash=sha256:885bde7d26e5b40e15c47bd5def48b38cbd50830a65f98122a8fb90962af7cd1 \ + --hash=sha256:8b4309a2775e4feee7356e63b163969a215d663399cce1b3d3b65e7ec2d9680e \ + --hash=sha256:8d77f92438daeaddc05682f0f3dac90c5b9829bcac75b57e8ce09cb67786073c \ + --hash=sha256:902425f5afe28572d65d2bf9c33edd5265c612ff82c69e6f83ea13eafc0dcbea \ + --hash=sha256:9bcc1e77fbd1609198966ded6b2a9897bd6c6bcbd2287a2fc7d75f1a254179c5 \ + --hash=sha256:a408c3c51358c89b29cfa5317cf11518b7ce5de1717abb55c5ae2d2921027de6 \ + --hash=sha256:a9bf8adc9e1f3012edc8f09b08336272aec0c55bc677422273e21280db748f7c \ + --hash=sha256:b818db35879d2edf7f46c7e729c700a0bce03b61b9412f5a7118406687cb151d \ + --hash=sha256:b8974b2a266b54c96709bd5e239979cddfd2dbceed331aa567ea1d7c4a2202db \ + --hash=sha256:be392ec3529e2f57faa28709d60723a763904f71a2b63aabe14fee6648fe3b14 \ + --hash=sha256:d3972b13148c1d1fbc092b27678a33b3080d1ac0ca305742b0119b75f9e87e38 \ + --hash=sha256:d40dcf533ca481355aa7b682e9e079f766f35715defa4929aeb5597f9604272e \ + --hash=sha256:e93df708c69a193fc7987192f94df250f83f3851fda49413f02ba5dded639482 \ + --hash=sha256:efd7e6660674e234e29937bc1481dceb7e0336bfae75b856b4fb272b5093c5d4 \ + --hash=sha256:f9b3a78f69dcbd803cf2fb3f972779875b244c1115481dfbdd567b2c22b31f6b \ + --hash=sha256:fa39475eaccb98f9199eccfda4298abaf35ae0caec676ffc25b3a5e224044464 \ + --hash=sha256:fbce6dae41b692a5973d0f2158f782b9ad05babc2c2019a970a1094a23909b1b + # via weasyprint +identify==2.6.13 \ + --hash=sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b \ + --hash=sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32 + # via pre-commit +nodeenv==1.9.1 \ + --hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \ + --hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9 + # via pre-commit +numpy==2.2.6 \ + --hash=sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff \ + --hash=sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47 \ + --hash=sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84 \ + --hash=sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d \ + --hash=sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6 \ + --hash=sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f \ + --hash=sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b \ + --hash=sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49 \ + --hash=sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163 \ + --hash=sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571 \ + --hash=sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42 \ + --hash=sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff \ + --hash=sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491 \ + --hash=sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4 \ + --hash=sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566 \ + --hash=sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf \ + --hash=sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40 \ + --hash=sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd \ + --hash=sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06 \ + --hash=sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282 \ + --hash=sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680 \ + --hash=sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db \ + --hash=sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3 \ + --hash=sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90 \ + --hash=sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1 \ + --hash=sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289 \ + --hash=sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab \ + --hash=sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c \ + --hash=sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d \ + --hash=sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb \ + --hash=sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d \ + --hash=sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a \ + --hash=sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf \ + --hash=sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1 \ + --hash=sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2 \ + --hash=sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a \ + --hash=sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543 \ + --hash=sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00 \ + --hash=sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c \ + --hash=sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f \ + --hash=sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd \ + --hash=sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868 \ + --hash=sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303 \ + --hash=sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83 \ + --hash=sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3 \ + --hash=sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d \ + --hash=sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87 \ + --hash=sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa \ + --hash=sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f \ + --hash=sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae \ + --hash=sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda \ + --hash=sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915 \ + --hash=sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249 \ + --hash=sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de \ + --hash=sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8 + # via opencv-python-headless +opencv-python-headless==4.12.0.88 \ + --hash=sha256:1e58d664809b3350c1123484dd441e1667cd7bed3086db1b9ea1b6f6cb20b50e \ + --hash=sha256:236c8df54a90f4d02076e6f9c1cc763d794542e886c576a6fee46ec8ff75a7a9 \ + --hash=sha256:365bb2e486b50feffc2d07a405b953a8f3e8eaa63865bc650034e5c71e7a5154 \ + --hash=sha256:86b413bdd6c6bf497832e346cd5371995de148e579b9774f8eba686dee3f5528 \ + --hash=sha256:aeb4b13ecb8b4a0beb2668ea07928160ea7c2cd2d9b5ef571bbee6bafe9cc8d0 \ + --hash=sha256:cfdc017ddf2e59b6c2f53bc12d74b6b0be7ded4ec59083ea70763921af2b6c09 \ + --hash=sha256:fde2cf5c51e4def5f2132d78e0c08f9c14783cd67356922182c6845b9af87dbd + # via -r .github\scripts\requirements_dev.in +pdf2image==1.17.0 \ + --hash=sha256:eaa959bc116b420dd7ec415fcae49b98100dda3dd18cd2fdfa86d09f112f6d57 \ + --hash=sha256:ecdd58d7afb810dffe21ef2b1bbc057ef434dabbac6c33778a38a3f7744a27e2 + # via -r .github\scripts\requirements_dev.in +pillow==11.3.0 \ + --hash=sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2 \ + --hash=sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214 \ + --hash=sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e \ + --hash=sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59 \ + --hash=sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50 \ + --hash=sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632 \ + --hash=sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06 \ + --hash=sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a \ + --hash=sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51 \ + --hash=sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced \ + --hash=sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f \ + --hash=sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12 \ + --hash=sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8 \ + --hash=sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6 \ + --hash=sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580 \ + --hash=sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f \ + --hash=sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac \ + --hash=sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860 \ + --hash=sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd \ + --hash=sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722 \ + --hash=sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8 \ + --hash=sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4 \ + --hash=sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673 \ + --hash=sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788 \ + --hash=sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542 \ + --hash=sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e \ + --hash=sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd \ + --hash=sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8 \ + --hash=sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523 \ + --hash=sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967 \ + --hash=sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809 \ + --hash=sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477 \ + --hash=sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027 \ + --hash=sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae \ + --hash=sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b \ + --hash=sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c \ + --hash=sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f \ + --hash=sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e \ + --hash=sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b \ + --hash=sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7 \ + --hash=sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27 \ + --hash=sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361 \ + --hash=sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae \ + --hash=sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d \ + --hash=sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc \ + --hash=sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58 \ + --hash=sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad \ + --hash=sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6 \ + --hash=sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024 \ + --hash=sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978 \ + --hash=sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb \ + --hash=sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d \ + --hash=sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0 \ + --hash=sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9 \ + --hash=sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f \ + --hash=sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874 \ + --hash=sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa \ + --hash=sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081 \ + --hash=sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149 \ + --hash=sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6 \ + --hash=sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d \ + --hash=sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd \ + --hash=sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f \ + --hash=sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c \ + --hash=sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31 \ + --hash=sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e \ + --hash=sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db \ + --hash=sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6 \ + --hash=sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f \ + --hash=sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494 \ + --hash=sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69 \ + --hash=sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94 \ + --hash=sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77 \ + --hash=sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d \ + --hash=sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7 \ + --hash=sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a \ + --hash=sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438 \ + --hash=sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288 \ + --hash=sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b \ + --hash=sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635 \ + --hash=sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3 \ + --hash=sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d \ + --hash=sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe \ + --hash=sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0 \ + --hash=sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe \ + --hash=sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a \ + --hash=sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805 \ + --hash=sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8 \ + --hash=sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36 \ + --hash=sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a \ + --hash=sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b \ + --hash=sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e \ + --hash=sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25 \ + --hash=sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12 \ + --hash=sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada \ + --hash=sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c \ + --hash=sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71 \ + --hash=sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d \ + --hash=sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c \ + --hash=sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6 \ + --hash=sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1 \ + --hash=sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50 \ + --hash=sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653 \ + --hash=sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c \ + --hash=sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4 \ + --hash=sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3 + # via + # -r .github\scripts\requirements_dev.in + # pdf2image + # weasyprint +platformdirs==4.3.8 \ + --hash=sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc \ + --hash=sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4 + # via virtualenv +pre-commit==4.3.0 \ + --hash=sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8 \ + --hash=sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16 + # via -r .github\scripts\requirements_dev.in +pycparser==2.22 \ + --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ + --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc + # via cffi +pydyf==0.11.0 \ + --hash=sha256:0aaf9e2ebbe786ec7a78ec3fbffa4cdcecde53fd6f563221d53c6bc1328848a3 \ + --hash=sha256:394dddf619cca9d0c55715e3c55ea121a9bf9cbc780cdc1201a2427917b86b64 + # via weasyprint +pyphen==0.17.2 \ + --hash=sha256:3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd \ + --hash=sha256:f60647a9c9b30ec6c59910097af82bc5dd2d36576b918e44148d8b07ef3b4aa3 + # via weasyprint +pyyaml==6.0.2 \ + --hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \ + --hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \ + --hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \ + --hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \ + --hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \ + --hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \ + --hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \ + --hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \ + --hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \ + --hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \ + --hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \ + --hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \ + --hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \ + --hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \ + --hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \ + --hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \ + --hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \ + --hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \ + --hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \ + --hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \ + --hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \ + --hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \ + --hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \ + --hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \ + --hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \ + --hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \ + --hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \ + --hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \ + --hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \ + --hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \ + --hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \ + --hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \ + --hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \ + --hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \ + --hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \ + --hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \ + --hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \ + --hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \ + --hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \ + --hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \ + --hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \ + --hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \ + --hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \ + --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \ + --hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \ + --hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \ + --hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \ + --hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \ + --hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \ + --hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \ + --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \ + --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \ + --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4 + # via pre-commit +tinycss2==1.4.0 \ + --hash=sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7 \ + --hash=sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289 + # via + # cssselect2 + # weasyprint +tinyhtml5==2.0.0 \ + --hash=sha256:086f998833da24c300c414d9fe81d9b368fd04cb9d2596a008421cbc705fcfcc \ + --hash=sha256:13683277c5b176d070f82d099d977194b7a1e26815b016114f581a74bbfbf47e + # via weasyprint +unoserver==3.3.2 \ + --hash=sha256:1eeb7467cf6b56b8eff3b576e2d1b2b2ff4e0eb2052e995ac80a1456de300639 \ + --hash=sha256:87e144f903ee21951b2e06a97549450c13ed7eca5bcebad942d3352d4e882616 + # via -r .github\scripts\requirements_dev.in +virtualenv==20.33.1 \ + --hash=sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67 \ + --hash=sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8 + # via pre-commit +weasyprint==66.0 \ + --hash=sha256:82b0783b726fcd318e2c977dcdddca76515b30044bc7a830cc4fbe717582a6d0 \ + --hash=sha256:da71dc87dc129ac9cffdc65e5477e90365ab9dbae45c744014ec1d06303dde40 + # via -r .github\scripts\requirements_dev.in +webencodings==0.5.1 \ + --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ + --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 + # via + # cssselect2 + # tinycss2 + # tinyhtml5 +zopfli==0.2.3.post1 \ + --hash=sha256:0aa5f90d6298bda02a95bc8dc8c3c19004d5a4e44bda00b67ca7431d857b4b54 \ + --hash=sha256:0cc20b02a9531559945324c38302fd4ba763311632d0ec8a1a0aa9c10ea363e6 \ + --hash=sha256:1d8cc06605519e82b16df090e17cb3990d1158861b2872c3117f1168777b81e4 \ + --hash=sha256:1f990634fd5c5c8ced8edddd8bd45fab565123b4194d6841e01811292650acae \ + --hash=sha256:2345e713260a350bea0b01a816a469ea356bc2d63d009a0d777691ecbbcf7493 \ + --hash=sha256:2768c877f76c8a0e7519b1c86c93757f3c01492ddde55751e9988afb7eff64e1 \ + --hash=sha256:29ea74e72ffa6e291b8c6f2504ce6c146b4fe990c724c1450eb8e4c27fd31431 \ + --hash=sha256:34a99592f3d9eb6f737616b5bd74b48a589fdb3cb59a01a50d636ea81d6af272 \ + --hash=sha256:3654bfc927bc478b1c3f3ff5056ed7b20a1a37fa108ca503256d0a699c03bbb1 \ + --hash=sha256:3657e416ffb8f31d9d3424af12122bb251befae109f2e271d87d825c92fc5b7b \ + --hash=sha256:37d011e92f7b9622742c905fdbed9920a1d0361df84142807ea2a528419dea7f \ + --hash=sha256:3827170de28faf144992d3d4dcf8f3998fe3c8a6a6f4a08f1d42c2ec6119d2bb \ + --hash=sha256:39e576f93576c5c223b41d9c780bbb91fd6db4babf3223d2a4fe7bf568e2b5a8 \ + --hash=sha256:3a89277ed5f8c0fb2d0b46d669aa0633123aa7381f1f6118c12f15e0fb48f8ca \ + --hash=sha256:3c163911f8bad94b3e1db0a572e7c28ba681a0c91d0002ea1e4fa9264c21ef17 \ + --hash=sha256:3f0197b6aa6eb3086ae9e66d6dd86c4d502b6c68b0ec490496348ae8c05ecaef \ + --hash=sha256:48dba9251060289101343110ab47c0756f66f809bb4d1ddbb6d5c7e7752115c5 \ + --hash=sha256:4915a41375bdee4db749ecd07d985a0486eb688a6619f713b7bf6fbfd145e960 \ + --hash=sha256:4c1226a7e2c7105ac31503a9bb97454743f55d88164d6d46bc138051b77f609b \ + --hash=sha256:4e50ffac74842c1c1018b9b73875a0d0a877c066ab06bf7cccbaa84af97e754f \ + --hash=sha256:518f1f4ed35dd69ce06b552f84e6d081f07c552b4c661c5312d950a0b764a58a \ + --hash=sha256:5aad740b4d4fcbaaae4887823925166ffd062db3b248b3f432198fc287381d1a \ + --hash=sha256:5f272186e03ad55e7af09ab78055535c201b1a0bcc2944edb1768298d9c483a4 \ + --hash=sha256:5fcfc0dc2761e4fcc15ad5d273b4d58c2e8e059d3214a7390d4d3c8e2aee644e \ + --hash=sha256:60db20f06c3d4c5934b16cfa62a2cc5c3f0686bffe0071ed7804d3c31ab1a04e \ + --hash=sha256:615a8ac9dda265e9cc38b2a76c3142e4a9f30fea4a79c85f670850783bc6feb4 \ + --hash=sha256:6482db9876c68faac2d20a96b566ffbf65ddaadd97b222e4e73641f4f8722fc4 \ + --hash=sha256:6617fb10f9e4393b331941861d73afb119cd847e88e4974bdbe8068ceef3f73f \ + --hash=sha256:676919fba7311125244eb0c4393679ac5fe856e5864a15d122bd815205369fa0 \ + --hash=sha256:6c2d2bc8129707e34c51f9352c4636ca313b52350bbb7e04637c46c1818a2a70 \ + --hash=sha256:71390dbd3fbf6ebea9a5d85ffed8c26ee1453ee09248e9b88486e30e0397b775 \ + --hash=sha256:716cdbfc57bfd3d3e31a58e6246e8190e6849b7dbb7c4ce39ef8bbf0edb8f6d5 \ + --hash=sha256:75a26a2307b10745a83b660c404416e984ee6fca515ec7f0765f69af3ce08072 \ + --hash=sha256:7be5cc6732eb7b4df17305d8a7b293223f934a31783a874a01164703bc1be6cd \ + --hash=sha256:7cce242b5df12b2b172489daf19c32e5577dd2fac659eb4b17f6a6efb446fd5c \ + --hash=sha256:81c341d9bb87a6dbbb0d45d6e272aca80c7c97b4b210f9b6e233bf8b87242f29 \ + --hash=sha256:89899641d4de97dbad8e0cde690040d078b6aea04066dacaab98e0b5a23573f2 \ + --hash=sha256:8d5ab297d660b75c159190ce6d73035502310e40fd35170aed7d1a1aea7ddd65 \ + --hash=sha256:8fbe5bcf10d01aab3513550f284c09fef32f342b36f56bfae2120a9c4d12c130 \ + --hash=sha256:91a2327a4d7e77471fa4fbb26991c6de4a738c6fc6a33e09bb25f56a870a4b7b \ + --hash=sha256:95a260cafd56b8fffa679918937401c80bb38e1681c448b988022e4c3610965d \ + --hash=sha256:96484dc0f48be1c5d7ae9f38ed1ce41e3675fd506b27c11a6607f14b49101e99 \ + --hash=sha256:9a6aec38a989bad7ddd1ef53f1265699e49e294d08231b5313d61293f3cd6237 \ + --hash=sha256:9ba214f4f45bec195ee8559651154d3ac2932470b9d91c5715fc29c013349f8c \ + --hash=sha256:9f4a7ec2770e6af05f5a02733fd3900f30a9cd58e5d6d3727e14c5bcd6e7d587 \ + --hash=sha256:a1cf720896d2ce998bc8e051d4b4ce0d8bec007aab6243102e8e1d22a0b2fb3f \ + --hash=sha256:a241a68581d34d67b40c425cce3d1fd211c092f99d9250947824ccba9f491949 \ + --hash=sha256:a53b18797cdef27e019db595d66c4b077325afe2fd62145953275f53d84ce40c \ + --hash=sha256:a82fc2dbebe6eb908b9c665e71496f8525c1bc4d2e3a7a7722ef2b128b6227c8 \ + --hash=sha256:a86eb88e06bd87e1fff31dac878965c26b0c26db59ddcf78bb0379a954b120de \ + --hash=sha256:aa588b21044f8a74e423d8c8a4c7fc9988501878aacced793467010039c50734 \ + --hash=sha256:b05296e8bc88c92e2b21e0a9bae4740c1551ee613c1d93a51fd28a7a0b2b6fbb \ + --hash=sha256:b0ec13f352ea5ae0fc91f98a48540512eed0767d0ec4f7f3cb92d92797983d18 \ + --hash=sha256:b3df42f52502438ee973042cc551877d24619fa1cd38ef7b7e9ac74200daca8b \ + --hash=sha256:b78008a69300d929ca2efeffec951b64a312e9a811e265ea4a907ab546d79fa6 \ + --hash=sha256:b9026a21b6d41eb0e2e63f5bc1242c3fcc43ecb770963cda99a4307863dac12e \ + --hash=sha256:bbe429fc50686bb2a2608a30843e36fbaa123462a5284f136c7d9e0145220bfd \ + --hash=sha256:bfa1eb759e07d8b7aa7a310a2bc535e127ee70addf90dc8d4b946b593c3e51a8 \ + --hash=sha256:c1e0ed5d84ffa2d677cc9582fc01e61dab2e7ef8b8996e055f0a76167b1b94df \ + --hash=sha256:c4278d1873ce6e803e5d4f8d702fd3026bd67fca744aa98881324d1157ddf748 \ + --hash=sha256:cac2b37ab21c2b36a10b685b1893ebd6b0f83ae26004838ac817680881576567 \ + --hash=sha256:cbe6df25807227519debd1a57ab236f5f6bad441500e85b13903e51f93a43214 \ + --hash=sha256:cd2c002f160502608dcc822ed2441a0f4509c52e86fcfd1a09e937278ed1ca14 \ + --hash=sha256:e0137dd64a493ba6a4be37405cfd6febe650a98cc1e9dca8f6b8c63b1db11b41 \ + --hash=sha256:e63d558847166543c2c9789e6f985400a520b7eacc4b99181668b2c3aeadd352 \ + --hash=sha256:eb45a34f23da4f8bc712b6376ca5396914b0b7c09adbb001dad964eb7f3132f8 \ + --hash=sha256:ecb7572df5372abce8073df078207d9d1749f20b8b136089916a4a0868d56051 \ + --hash=sha256:f12000a6accdd4bf0a3fa6eaa1b1c7a7bc80af0a2edf3f89d770d3dcce1d0e22 \ + --hash=sha256:f7d69c1a7168ad0e9cb864e8663acb232986a0c9c9cb9801f56bf6214f53a54d \ + --hash=sha256:f815fcc2b2a457977724bad97fb4854022980f51ce7b136925e336b530545ae1 \ + --hash=sha256:fc39f5c27f962ec8660d8d20c24762431131b5d8c672b44b0a54cf2b5bcde9b9 + # via fonttools + +# The following packages are considered to be unsafe in a requirements file: +pip==25.2 \ + --hash=sha256:578283f006390f85bb6282dffb876454593d637f5d1be494b5202ce4877e71f2 \ + --hash=sha256:6d67a2b4e7f14d8b31b8b52648866fa717f45a1eb70e83002f4331d07e953717 + # via -r .github\scripts\requirements_dev.in +setuptools==80.9.0 \ + --hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 \ + --hash=sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c + # via -r .github\scripts\requirements_dev.in diff --git a/Dockerfile.dev b/Dockerfile.dev index 48084878d..eba01cf02 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -34,10 +34,10 @@ ENV SETUPTOOLS_USE_DISTUTILS=local \ TMP=/tmp/stirling-pdf # Installation der benötigten Python-Pakete +COPY .github/scripts/requirements_dev.txt /tmp/requirements_dev.txt RUN python3 -m venv --system-site-packages /opt/venv \ && . /opt/venv/bin/activate \ - && pip install --no-cache-dir --upgrade pip setuptools \ - && pip install --no-cache-dir WeasyPrint pdf2image pillow unoserver opencv-python-headless pre-commit + && pip install --no-cache-dir --require-hashes -r /tmp/requirements_dev.txt # Füge den venv-Pfad zur globalen PATH-Variable hinzu, damit die Tools verfügbar sind ENV PATH="/opt/venv/bin:$PATH" From 40cf337b23444a3204b6707a3c423accf1397ffa Mon Sep 17 00:00:00 2001 From: Ludy Date: Sun, 24 Aug 2025 23:08:29 +0200 Subject: [PATCH 19/20] feat(ssrf): enhance private IP detection and IPv6 handling (#4191) # Description of Changes - Refactored `isPrivateAddress` to improve detection of private and local addresses for both IPv4 and IPv6. - Added explicit handling for: - IPv4-mapped IPv6 addresses - IPv6 link-local, site-local, and unique local (fc00::/7) addresses - Additional IPv4 private ranges such as link-local (169.254.0.0/16) - Introduced `normalizeIpv4MappedAddress` to standardize IP checks in cloud metadata detection. - Replaced `switch` statement with modern `switch` expression for cleaner control flow. These changes were made to strengthen SSRF protection by covering more address edge cases, especially in mixed IPv4/IPv6 environments. This also improves detection of cloud metadata endpoints when accessed via IPv4-mapped IPv6 addresses. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../common/service/SsrfProtectionService.java | 94 +++++++++++++++---- 1 file changed, 76 insertions(+), 18 deletions(-) diff --git a/app/common/src/main/java/stirling/software/common/service/SsrfProtectionService.java b/app/common/src/main/java/stirling/software/common/service/SsrfProtectionService.java index 97c2da12e..b58e0d516 100644 --- a/app/common/src/main/java/stirling/software/common/service/SsrfProtectionService.java +++ b/app/common/src/main/java/stirling/software/common/service/SsrfProtectionService.java @@ -1,5 +1,7 @@ package stirling.software.common.service; +import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; @@ -51,16 +53,12 @@ public class SsrfProtectionService { SsrfProtectionLevel level = parseProtectionLevel(config.getLevel()); - switch (level) { - case OFF: - return true; - case MAX: - return isMaxSecurityAllowed(trimmedUrl, config); - case MEDIUM: - return isMediumSecurityAllowed(trimmedUrl, config); - default: - return false; - } + return switch (level) { + case OFF -> true; + case MAX -> isMaxSecurityAllowed(trimmedUrl, config); + case MEDIUM -> isMediumSecurityAllowed(trimmedUrl, config); + default -> false; + }; } private SsrfProtectionLevel parseProtectionLevel(String level) { @@ -172,15 +170,61 @@ public class SsrfProtectionService { } private boolean isPrivateAddress(InetAddress address) { - return address.isSiteLocalAddress() - || address.isAnyLocalAddress() - || isPrivateIPv4Range(address.getHostAddress()); + if (address.isAnyLocalAddress() || address.isLoopbackAddress()) { + return true; + } + + if (address instanceof Inet4Address) { + return isPrivateIPv4Range(address.getHostAddress()); + } + + if (address instanceof Inet6Address addr6) { + if (addr6.isLinkLocalAddress() || addr6.isSiteLocalAddress()) { + return true; + } + + byte[] bytes = addr6.getAddress(); + if (isIpv4MappedAddress(bytes)) { + String ipv4 = + (bytes[12] & 0xff) + + "." + + (bytes[13] & 0xff) + + "." + + (bytes[14] & 0xff) + + "." + + (bytes[15] & 0xff); + return isPrivateIPv4Range(ipv4); + } + + int firstByte = bytes[0] & 0xff; + // Check for IPv6 unique local addresses (fc00::/7) + if ((firstByte & 0xfe) == 0xfc) { + return true; + } + } + + return false; + } + + private boolean isIpv4MappedAddress(byte[] addr) { + if (addr.length != 16) { + return false; + } + for (int i = 0; i < 10; i++) { + if (addr[i] != 0) { + return false; + } + } + // For IPv4-mapped IPv6 addresses, bytes 10 and 11 must be 0xff (i.e., address is ::ffff:w.x.y.z) + return addr[10] == (byte) 0xff && addr[11] == (byte) 0xff; } private boolean isPrivateIPv4Range(String ip) { + // Includes RFC1918, loopback, link-local, and unspecified addresses return ip.startsWith("10.") || ip.startsWith("192.168.") || (ip.startsWith("172.") && isInRange172(ip)) + || ip.startsWith("169.254.") || ip.startsWith("127.") || "0.0.0.0".equals(ip); } @@ -192,17 +236,31 @@ public class SsrfProtectionService { int secondOctet = Integer.parseInt(parts[1]); return secondOctet >= 16 && secondOctet <= 31; } catch (NumberFormatException e) { - return false; } } return false; } private boolean isCloudMetadataAddress(String ip) { + String normalizedIp = normalizeIpv4MappedAddress(ip); // Cloud metadata endpoints for AWS, GCP, Azure, Oracle Cloud, and IBM Cloud - return ip.startsWith("169.254.169.254") // AWS/GCP/Azure - || ip.startsWith("fd00:ec2::254") // AWS IPv6 - || ip.startsWith("169.254.169.253") // Oracle Cloud - || ip.startsWith("169.254.169.250"); // IBM Cloud + return normalizedIp.startsWith("169.254.169.254") // AWS/GCP/Azure + || normalizedIp.startsWith("fd00:ec2::254") // AWS IPv6 + || normalizedIp.startsWith("169.254.169.253") // Oracle Cloud + || normalizedIp.startsWith("169.254.169.250"); // IBM Cloud + } + + private String normalizeIpv4MappedAddress(String ip) { + if (ip == null) { + return ""; + } + if (ip.startsWith("::ffff:")) { + return ip.substring(7); + } + int lastColon = ip.lastIndexOf(':'); + if (lastColon >= 0 && ip.indexOf('.') > lastColon) { + return ip.substring(lastColon + 1); + } + return ip; } } From 3af93f0adb0eec9ef895ba2bb901f7ba812a5f8e Mon Sep 17 00:00:00 2001 From: Ludy Date: Sun, 24 Aug 2025 23:16:55 +0200 Subject: [PATCH 20/20] feat(database,Jwt): relocate backups and Jwt-keys to `config/backup` and add Enterprise cleanup endpoints (#4225) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description of Changes - **What was changed** - Centralized installation paths: - Introduced `BACKUP_PATH`, `BACKUP_DB_PATH`, and `BACKUP_PRIVATE_KEY_PATH` in `InstallationPathConfig`; `getPrivateKeyPath()` now resolves to `backup/keys` and new `getBackupPath()` returns `backup/db`. - Removed old `PRIVATE_KEY_PATH` and switched all usages to the new locations. - Database service enhancements: - `DatabaseService` now uses `InstallationPathConfig.getBackupPath()` and includes a one-time migration to move existing backups from `config/db/backup` to `config/backup/db` (**@Deprecated(since = "2.0.0", forRemoval = true)**). - Added `deleteAllBackups()` and `deleteLastBackup()` methods and exposed them via a new Enterprise controller. - New Enterprise-only API: - Added `DatabaseControllerEnterprise` with: - `DELETE /api/v1/database/deleteAll` — delete all backup files. - `DELETE /api/v1/database/deleteLast` — delete the most recent backup. - Endpoints gated by `@EnterpriseEndpoint` and `@Conditional(H2SQLCondition.class)`. - Key persistence adjustments: - `KeyPersistenceService` now migrates keys from `config/db/keys` to `config/backup/keys` on startup (**@Deprecated(since = "2.0.0", forRemoval = true)**). - Miscellaneous refactors/fixes: - Switched driver resolution in `DatabaseConfig` to a switch expression. - Corrected HTTP status usage to `HttpStatus.SEE_OTHER`. - Removed constructor `runningEE` flag from `AccountWebController` and replaced EE checks with `@EnterpriseEndpoint`. - Minor test and annotation improvements (e.g., `@Deprecated(since = "0.45.0")`, method references, equals order). - **Why the change was made** - To standardize and future-proof storage locations for both backups and keys under a clear `config/backup` hierarchy. - To give Enterprise admins first-class, safe cleanup endpoints for managing backup retention without manual file operations. - To reduce conditional logic in controllers and rely on declarative EE gating. - To improve maintainability and correctness (status codes, switch expression, null-safety patterns). --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../configuration/InstallationPathConfig.java | 20 +++- .../model/ApplicationPropertiesLogicTest.java | 2 +- .../security/config/AccountWebController.java | 20 ++-- .../configuration/DatabaseConfig.java | 8 +- .../controller/api/DatabaseController.java | 4 +- .../DatabaseControllerEnterprise.java | 101 ++++++++++++++++++ .../security/service/DatabaseService.java | 85 ++++++++++++++- .../service/DatabaseServiceInterface.java | 6 ++ .../service/KeyPersistenceService.java | 31 ++++++ .../proprietary/service/AuditService.java | 4 +- 10 files changed, 252 insertions(+), 29 deletions(-) create mode 100644 app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/enterprise/DatabaseControllerEnterprise.java diff --git a/app/common/src/main/java/stirling/software/common/configuration/InstallationPathConfig.java b/app/common/src/main/java/stirling/software/common/configuration/InstallationPathConfig.java index 64fbc41b7..860e02806 100644 --- a/app/common/src/main/java/stirling/software/common/configuration/InstallationPathConfig.java +++ b/app/common/src/main/java/stirling/software/common/configuration/InstallationPathConfig.java @@ -14,18 +14,22 @@ public class InstallationPathConfig { private static final String CONFIG_PATH; private static final String CUSTOM_FILES_PATH; private static final String CLIENT_WEBUI_PATH; - private static final String SCRIPTS_PATH; private static final String PIPELINE_PATH; // Config paths private static final String SETTINGS_PATH; private static final String CUSTOM_SETTINGS_PATH; + private static final String SCRIPTS_PATH; + private static final String BACKUP_PATH; + + // Backup paths + private static final String BACKUP_DB_PATH; + private static final String BACKUP_PRIVATE_KEY_PATH; // Custom file paths private static final String STATIC_PATH; private static final String TEMPLATES_PATH; private static final String SIGNATURES_PATH; - private static final String PRIVATE_KEY_PATH; static { BASE_PATH = initializeBasePath(); @@ -41,12 +45,16 @@ public class InstallationPathConfig { SETTINGS_PATH = CONFIG_PATH + "settings.yml"; CUSTOM_SETTINGS_PATH = CONFIG_PATH + "custom_settings.yml"; SCRIPTS_PATH = CONFIG_PATH + "scripts" + File.separator; + BACKUP_PATH = CONFIG_PATH + "backup" + File.separator; + + // Initialize backup paths + BACKUP_DB_PATH = BACKUP_PATH + "db" + File.separator; + BACKUP_PRIVATE_KEY_PATH = BACKUP_PATH + "keys" + File.separator; // Initialize custom file paths STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator; TEMPLATES_PATH = CUSTOM_FILES_PATH + "templates" + File.separator; SIGNATURES_PATH = CUSTOM_FILES_PATH + "signatures" + File.separator; - PRIVATE_KEY_PATH = CONFIG_PATH + "db" + File.separator + "keys" + File.separator; } private static String initializeBasePath() { @@ -124,6 +132,10 @@ public class InstallationPathConfig { } public static String getPrivateKeyPath() { - return PRIVATE_KEY_PATH; + return BACKUP_PRIVATE_KEY_PATH; + } + + public static String getBackupPath() { + return BACKUP_DB_PATH; } } diff --git a/app/common/src/test/java/stirling/software/common/model/ApplicationPropertiesLogicTest.java b/app/common/src/test/java/stirling/software/common/model/ApplicationPropertiesLogicTest.java index da83fd462..c8d877dc9 100644 --- a/app/common/src/test/java/stirling/software/common/model/ApplicationPropertiesLogicTest.java +++ b/app/common/src/test/java/stirling/software/common/model/ApplicationPropertiesLogicTest.java @@ -179,7 +179,7 @@ class ApplicationPropertiesLogicTest { assertEquals(30, t.getOcrMyPdfTimeoutMinutes()); } - @Deprecated + @Deprecated(since = "0.45.0") @Test void enterprise_metadata_defaults() { ApplicationProperties.EnterpriseEdition ee = new ApplicationProperties.EnterpriseEdition(); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java index 46d0e7d3d..a61a7b0fa 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java @@ -11,7 +11,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; @@ -59,19 +58,16 @@ public class AccountWebController { private final SessionPersistentRegistry sessionPersistentRegistry; // Assuming you have a repository for user operations private final UserRepository userRepository; - private final boolean runningEE; private final TeamRepository teamRepository; public AccountWebController( ApplicationProperties applicationProperties, SessionPersistentRegistry sessionPersistentRegistry, UserRepository userRepository, - TeamRepository teamRepository, - @Qualifier("runningEE") boolean runningEE) { + TeamRepository teamRepository) { this.applicationProperties = applicationProperties; this.sessionPersistentRegistry = sessionPersistentRegistry; this.userRepository = userRepository; - this.runningEE = runningEE; this.teamRepository = teamRepository; } @@ -207,11 +203,9 @@ public class AccountWebController { } @PreAuthorize("hasRole('ROLE_ADMIN')") + @EnterpriseEndpoint @GetMapping("/usage") public String showUsage() { - if (!runningEE) { - return "error"; - } return "usage"; } @@ -243,7 +237,7 @@ public class AccountWebController { // Also check if user is part of the Internal team if (user.getTeam() != null - && user.getTeam().getName().equals(TeamService.INTERNAL_TEAM_NAME)) { + && TeamService.INTERNAL_TEAM_NAME.equals(user.getTeam().getName())) { shouldRemove = true; } @@ -362,11 +356,9 @@ public class AccountWebController { teamRepository.findAll().stream() .filter( team -> - !team.getName() - .equals( - stirling.software.proprietary.security - .service.TeamService - .INTERNAL_TEAM_NAME)) + !stirling.software.proprietary.security.service.TeamService + .INTERNAL_TEAM_NAME + .equals(team.getName())) .toList(); model.addAttribute("teams", allTeams); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/DatabaseConfig.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/DatabaseConfig.java index e6afa6e40..625dc041a 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/DatabaseConfig.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/DatabaseConfig.java @@ -134,21 +134,21 @@ public class DatabaseConfig { ApplicationProperties.Driver driver = ApplicationProperties.Driver.valueOf(driverName.toUpperCase()); - switch (driver) { + return switch (driver) { case H2 -> { log.debug("H2 driver selected"); - return DatabaseDriver.H2.getDriverClassName(); + yield DatabaseDriver.H2.getDriverClassName(); } case POSTGRESQL -> { log.debug("Postgres driver selected"); - return DatabaseDriver.POSTGRESQL.getDriverClassName(); + yield DatabaseDriver.POSTGRESQL.getDriverClassName(); } default -> { log.warn("{} driver selected", driverName); throw new UnsupportedProviderException( driverName + " is not currently supported"); } - } + }; } catch (IllegalArgumentException e) { log.warn("Unknown driver: {}", driverName); throw new UnsupportedProviderException(driverName + " is not currently supported"); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/DatabaseController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/DatabaseController.java index dec64c46f..ca520a20d 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/DatabaseController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/DatabaseController.java @@ -7,10 +7,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import org.eclipse.jetty.http.HttpStatus; import org.springframework.context.annotation.Conditional; import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -145,7 +145,7 @@ public class DatabaseController { .body(resource); } catch (IOException e) { log.error("Error downloading file: {}", e.getMessage()); - return ResponseEntity.status(HttpStatus.SEE_OTHER_303) + return ResponseEntity.status(HttpStatus.SEE_OTHER) .location(URI.create("/database?error=downloadFailed")) .build(); } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/enterprise/DatabaseControllerEnterprise.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/enterprise/DatabaseControllerEnterprise.java new file mode 100644 index 000000000..b1da460b0 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/enterprise/DatabaseControllerEnterprise.java @@ -0,0 +1,101 @@ +package stirling.software.proprietary.security.controller.api.enterprise; + +import java.util.List; + +import org.apache.commons.lang3.tuple.Pair; +import org.springframework.context.annotation.Conditional; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import stirling.software.common.model.FileInfo; +import stirling.software.proprietary.security.config.EnterpriseEndpoint; +import stirling.software.proprietary.security.database.H2SQLCondition; +import stirling.software.proprietary.security.service.DatabaseService; + +@Slf4j +@Controller +@RequestMapping("/api/v1/database") +@PreAuthorize("hasRole('ROLE_ADMIN')") +@EnterpriseEndpoint +@Conditional(H2SQLCondition.class) +@Tag(name = "Database", description = "Database APIs for backup, import, and management") +@RequiredArgsConstructor +public class DatabaseControllerEnterprise { + + private final DatabaseService databaseService; + + @Operation( + summary = "Delete the last database backup file", + description = + "Only Enterprise - Deletes the last database backup file from the server.") + @DeleteMapping("/deleteLast") + public ResponseEntity deleteLastFile() { + log.info("Deleting last database backup file..."); + List> results = databaseService.deleteLastBackup(); + return getDeleteAllResults(results); + } + + @Operation( + summary = "Delete all database backup files", + description = "Only Enterprise - Deletes all database backup files from the server.") + @DeleteMapping("/deleteAll") + public ResponseEntity deleteAllFiles() { + log.info("Deleting all database backup files..."); + List> results = databaseService.deleteAllBackups(); + return getDeleteAllResults(results); + } + + private ResponseEntity getDeleteAllResults(List> results) { + if (results.isEmpty()) { + log.info("No backup files found to delete."); + return ResponseEntity.ok(new DeleteAllResult(List.of(), List.of(), "noContent")); + } + + List deleted = + results.stream() + .filter(p -> Boolean.TRUE.equals(p.getRight())) + .map(p -> p.getLeft().getFileName()) + .toList(); + + List failed = + results.stream() + .filter(p -> !Boolean.TRUE.equals(p.getRight())) + .map(p -> p.getLeft().getFileName()) + .toList(); + + log.info("Deleted backup files: {}", deleted); + if (!failed.isEmpty()) { + log.warn("Some backup files could not be deleted: {}", failed); + return ResponseEntity.status(HttpStatus.MULTI_STATUS) // 207 + .body(new DeleteAllResult(deleted, failed, "partialFailure")); + } + DeleteAllResult result = new DeleteAllResult(deleted, failed, "ok"); + log.debug( + "DeleteAllResult: deleted={}, failed={}, status={}", + result.deleted, + result.failed, + result.status); + return ResponseEntity.ok(result); // 200 + } + + private static final class DeleteAllResult { + public final List deleted; + public final List failed; + public final String status; + + public DeleteAllResult(List deleted, List failed, String status) { + this.deleted = deleted; + this.failed = failed; + this.status = status; + } + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/DatabaseService.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/DatabaseService.java index 6474ae7ea..1a3f3ee9c 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/DatabaseService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/DatabaseService.java @@ -5,6 +5,7 @@ import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.sql.Connection; import java.sql.PreparedStatement; @@ -21,6 +22,7 @@ import java.util.stream.Collectors; import javax.sql.DataSource; +import org.apache.commons.lang3.tuple.Pair; import org.springframework.jdbc.datasource.init.CannotReadScriptException; import org.springframework.jdbc.datasource.init.ScriptException; import org.springframework.stereotype.Service; @@ -45,10 +47,39 @@ public class DatabaseService implements DatabaseServiceInterface { public DatabaseService( ApplicationProperties.Datasource datasourceProps, DataSource dataSource) { - this.BACKUP_DIR = - Paths.get(InstallationPathConfig.getConfigPath(), "db", "backup").normalize(); + this.BACKUP_DIR = Paths.get(InstallationPathConfig.getBackupPath()).normalize(); this.datasourceProps = datasourceProps; this.dataSource = dataSource; + moveBackupFiles(); + } + + /** Move all backup files from db/backup to backup/db */ + @Deprecated(since = "2.0.0", forRemoval = true) + private void moveBackupFiles() { + Path sourceDir = + Paths.get(InstallationPathConfig.getConfigPath(), "db", "backup").normalize(); + + if (!Files.exists(sourceDir)) { + log.info("Source directory does not exist: {}", sourceDir); + return; + } + + try { + Files.createDirectories(BACKUP_DIR); + try (DirectoryStream stream = Files.newDirectoryStream(sourceDir)) { + for (Path entry : stream) { + if (entry.getFileName().toString().startsWith(BACKUP_PREFIX) + && entry.getFileName().toString().endsWith(SQL_SUFFIX)) { + Files.move( + entry, + BACKUP_DIR.resolve(entry.getFileName()), + StandardCopyOption.REPLACE_EXISTING); + } + } + } + } catch (IOException e) { + log.error("Error moving backup files: {}", e.getMessage(), e); + } } /** @@ -198,6 +229,46 @@ public class DatabaseService implements DatabaseServiceInterface { } } + @Override + public List> deleteAllBackups() { + List backupList = this.getBackupList(); + List> deletedFiles = new ArrayList<>(); + + for (FileInfo backup : backupList) { + try { + Files.deleteIfExists(Paths.get(backup.getFilePath())); + deletedFiles.add(Pair.of(backup, true)); + } catch (IOException e) { + log.error("Error deleting backup file: {}", backup.getFileName(), e); + deletedFiles.add(Pair.of(backup, false)); + } + } + return deletedFiles; + } + + @Override + public List> deleteLastBackup() { + + List backupList = this.getBackupList(); + List> deletedFiles = new ArrayList<>(); + if (!backupList.isEmpty()) { + FileInfo lastBackup = backupList.get(backupList.size() - 1); + try { + Files.deleteIfExists(Paths.get(lastBackup.getFilePath())); + deletedFiles.add(Pair.of(lastBackup, true)); + } catch (IOException e) { + log.error("Error deleting last backup file: {}", lastBackup.getFileName(), e); + deletedFiles.add(Pair.of(lastBackup, false)); + } + } + return deletedFiles; + } + + /** + * Deletes the oldest backup file from the specified list. + * + * @param filteredBackupList the list of backup files + */ private static void deleteOldestBackup(List filteredBackupList) { try { filteredBackupList.sort( @@ -237,6 +308,11 @@ public class DatabaseService implements DatabaseServiceInterface { return version; } + /* + * Checks if the current datasource is H2. + * + * @return true if the datasource is H2, false otherwise + */ private boolean isH2Database() { boolean isTypeH2 = datasourceProps.getType().equalsIgnoreCase(ApplicationProperties.Driver.H2.name()); @@ -301,6 +377,11 @@ public class DatabaseService implements DatabaseServiceInterface { return filePath; } + /** + * Executes a database script. + * + * @param scriptPath the path to the script file + */ private void executeDatabaseScript(Path scriptPath) { if (isH2Database()) { String query = "RUNSCRIPT from ?;"; diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/DatabaseServiceInterface.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/DatabaseServiceInterface.java index 613432f0a..d0ba033d6 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/DatabaseServiceInterface.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/DatabaseServiceInterface.java @@ -3,6 +3,8 @@ package stirling.software.proprietary.security.service; import java.sql.SQLException; import java.util.List; +import org.apache.commons.lang3.tuple.Pair; + import stirling.software.common.model.FileInfo; import stirling.software.common.model.exception.UnsupportedProviderException; @@ -14,4 +16,8 @@ public interface DatabaseServiceInterface { boolean hasBackup(); List getBackupList(); + + List> deleteAllBackups(); + + List> deleteLastBackup(); } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/KeyPersistenceService.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/KeyPersistenceService.java index cc07fbbc7..eb02b6368 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/KeyPersistenceService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/KeyPersistenceService.java @@ -1,9 +1,11 @@ package stirling.software.proprietary.security.service; import java.io.IOException; +import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -52,6 +54,34 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface { this.verifyingKeyCache = cacheManager.getCache("verifyingKeys"); } + /** Move all key files from db/keys to backup/keys */ + @Deprecated(since = "2.0.0", forRemoval = true) + private void moveKeysToBackup() { + Path sourceDir = + Paths.get(InstallationPathConfig.getConfigPath(), "db", "keys").normalize(); + + if (!Files.exists(sourceDir)) { + log.info("Source directory does not exist: {}", sourceDir); + return; + } + + Path targetDir = Paths.get(InstallationPathConfig.getPrivateKeyPath()).normalize(); + + try { + Files.createDirectories(targetDir); + try (DirectoryStream stream = Files.newDirectoryStream(sourceDir)) { + for (Path entry : stream) { + Files.move( + entry, + targetDir.resolve(entry.getFileName()), + StandardCopyOption.REPLACE_EXISTING); + } + } + } catch (IOException e) { + log.error("Error moving key files to backup: {}", e.getMessage(), e); + } + } + @PostConstruct public void initializeKeystore() { if (!isKeystoreEnabled()) { @@ -59,6 +89,7 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface { } try { + moveKeysToBackup(); ensurePrivateKeyDirectoryExists(); loadKeyPair(); } catch (Exception e) { diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/AuditService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/AuditService.java index 73b57286b..57ae13c18 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/AuditService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/AuditService.java @@ -2,6 +2,7 @@ package stirling.software.proprietary.service; import java.util.Map; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.actuate.audit.AuditEvent; import org.springframework.boot.actuate.audit.AuditEventRepository; import org.springframework.security.core.Authentication; @@ -29,8 +30,7 @@ public class AuditService { public AuditService( AuditEventRepository repository, AuditConfigurationProperties auditConfig, - @org.springframework.beans.factory.annotation.Qualifier("runningEE") - boolean runningEE) { + @Qualifier("runningEE") boolean runningEE) { this.repository = repository; this.auditConfig = auditConfig; this.runningEE = runningEE;