mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-06 18:30:57 +00:00
Merge branch 'main' into bug/csrf-decryption-API
This commit is contained in:
commit
1f39481efe
91
.github/workflows/multiOSReleases.yml
vendored
Normal file
91
.github/workflows/multiOSReleases.yml
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
name: Test Installers Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
jobs:
|
||||||
|
build-installers:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: windows-latest
|
||||||
|
platform: win
|
||||||
|
ext: exe
|
||||||
|
#- os: macos-latest
|
||||||
|
# platform: mac
|
||||||
|
# ext: dmg
|
||||||
|
#- os: ubuntu-latest
|
||||||
|
# platform: linux
|
||||||
|
# ext: deb
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: "21"
|
||||||
|
distribution: "temurin"
|
||||||
|
|
||||||
|
- uses: gradle/actions/setup-gradle@v4
|
||||||
|
with:
|
||||||
|
gradle-version: 8.7
|
||||||
|
|
||||||
|
# Install Windows dependencies
|
||||||
|
- name: Install WiX Toolset
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
run: |
|
||||||
|
curl -L -o wix.exe https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314.exe
|
||||||
|
.\wix.exe /install /quiet
|
||||||
|
|
||||||
|
# Install Linux dependencies
|
||||||
|
- name: Install Linux Dependencies
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y fakeroot rpm
|
||||||
|
|
||||||
|
# Get version number
|
||||||
|
- name: Get version number
|
||||||
|
id: versionNumber
|
||||||
|
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Get version number mac
|
||||||
|
id: versionNumberMac
|
||||||
|
run: echo "versionNumberMac=$(./gradlew printMacVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
# Build installer
|
||||||
|
- name: Build Installer
|
||||||
|
run: ./gradlew build jpackage -x test --info
|
||||||
|
env:
|
||||||
|
DOCKER_ENABLE_SECURITY: false
|
||||||
|
STIRLING_PDF_DESKTOP_UI: true
|
||||||
|
|
||||||
|
# Rename and collect artifacts based on OS
|
||||||
|
- name: Prepare artifacts
|
||||||
|
id: prepare
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [ "${{ matrix.os }}" = "windows-latest" ]; then
|
||||||
|
mv "build/jpackage/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.exe" "Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}"
|
||||||
|
elif [ "${{ matrix.os }}" = "macos-latest" ]; then
|
||||||
|
mv "build/jpackage/Stirling-PDF-${{ steps.versionNumberMac.outputs.versionNumberMac }}.dmg" "Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}"
|
||||||
|
else
|
||||||
|
mv "build/jpackage/stirling-pdf_${{ steps.versionNumber.outputs.versionNumber }}-1_amd64.deb" "Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Upload installer as artifact for testing
|
||||||
|
- name: Upload Installer Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Stirling-PDF-${{ matrix.platform }}-installer.{{ matrix.ext }}
|
||||||
|
path: Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}-${{ matrix.platform }}.${{ matrix.ext }}
|
||||||
|
retention-days: 1
|
||||||
|
if-no-files-found: error
|
59
.github/workflows/releaseArtifacts.yml
vendored
59
.github/workflows/releaseArtifacts.yml
vendored
@ -35,36 +35,37 @@ jobs:
|
|||||||
run: ./gradlew clean createExe
|
run: ./gradlew clean createExe
|
||||||
env:
|
env:
|
||||||
DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }}
|
DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }}
|
||||||
|
STIRLING_PDF_DESKTOP_UI: false
|
||||||
|
|
||||||
- name: Get version number
|
- name: Get version number
|
||||||
id: versionNumber
|
id: versionNumber
|
||||||
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Rename binarie
|
- name: Rename binarie
|
||||||
if: matrix.file_suffix != ''
|
run: cp ./build/launch4j/Stirling-PDF.exe ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
||||||
run: cp ./build/launch4j/Stirling-PDF.exe ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe
|
|
||||||
|
|
||||||
- name: Upload Assets binarie
|
- name: Upload Assets binarie
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe
|
path: ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
||||||
name: Stirling-PDF${{ matrix.file_suffix }}.exe
|
name: Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
||||||
overwrite: true
|
overwrite: true
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload binaries to release
|
- name: Upload binaries to release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
files: ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe
|
files: ./build/launch4j/Stirling-PDF-Server${{ matrix.file_suffix }}.exe
|
||||||
|
|
||||||
- name: Rename jar binaries
|
- name: Rename jar binaries
|
||||||
run: cp ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
run: cp ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar ./build/libs/Stirling-PDF-Server${{ matrix.file_suffix }}.jar
|
||||||
|
|
||||||
- name: Upload Assets jar binaries
|
- name: Upload Assets jar binaries
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
path: ./build/libs/Stirling-PDF-Server${{ matrix.file_suffix }}.jar
|
||||||
name: Stirling-PDF${{ matrix.file_suffix }}.jar
|
name: Stirling-PDF-Server${{ matrix.file_suffix }}.jar
|
||||||
overwrite: true
|
overwrite: true
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
@ -72,4 +73,44 @@ jobs:
|
|||||||
- name: Upload jar binaries to release
|
- name: Upload jar binaries to release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
files: ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
files: ./build/libs/Stirling-PDF-Server${{ matrix.file_suffix }}.jar
|
||||||
|
|
||||||
|
|
||||||
|
push-ui:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: "17"
|
||||||
|
distribution: "temurin"
|
||||||
|
|
||||||
|
- uses: gradle/actions/setup-gradle@v4
|
||||||
|
with:
|
||||||
|
gradle-version: 8.7
|
||||||
|
|
||||||
|
- name: Generate exe
|
||||||
|
run: ./gradlew clean createExe
|
||||||
|
env:
|
||||||
|
DOCKER_ENABLE_SECURITY: false
|
||||||
|
STIRLING_PDF_DESKTOP_UI: true
|
||||||
|
|
||||||
|
- name: Get version number
|
||||||
|
id: versionNumber
|
||||||
|
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Upload Assets binarie
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ./build/launch4j/Stirling-PDF.exe
|
||||||
|
name: Stirling-PDF.exe
|
||||||
|
overwrite: true
|
||||||
|
retention-days: 1
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload binaries to release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: ./build/launch4j/Stirling-PDF.exe
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -161,3 +161,4 @@ out/
|
|||||||
.pytest_cache
|
.pytest_cache
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
**/jcef-bundle/
|
@ -1,5 +1,5 @@
|
|||||||
# use alpine
|
# use alpine
|
||||||
FROM alpine:3.20.3
|
FROM alpine:3.21.0
|
||||||
|
|
||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
|
48
README.md
48
README.md
@ -191,44 +191,44 @@ Stirling-PDF currently supports 38 languages!
|
|||||||
|
|
||||||
| Language | Progress |
|
| Language | Progress |
|
||||||
| -------------------------------------------- | -------------------------------------- |
|
| -------------------------------------------- | -------------------------------------- |
|
||||||
| Arabic (العربية) (ar_AR) |  |
|
| Arabic (العربية) (ar_AR) |  |
|
||||||
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
||||||
| Basque (Euskara) (eu_ES) |  |
|
| Basque (Euskara) (eu_ES) |  |
|
||||||
| Bulgarian (Български) (bg_BG) |  |
|
| Bulgarian (Български) (bg_BG) |  |
|
||||||
| Catalan (Català) (ca_CA) |  |
|
| Catalan (Català) (ca_CA) |  |
|
||||||
| Croatian (Hrvatski) (hr_HR) |  |
|
| Croatian (Hrvatski) (hr_HR) |  |
|
||||||
| Czech (Česky) (cs_CZ) |  |
|
| Czech (Česky) (cs_CZ) |  |
|
||||||
| Danish (Dansk) (da_DK) |  |
|
| Danish (Dansk) (da_DK) |  |
|
||||||
| Dutch (Nederlands) (nl_NL) |  |
|
| Dutch (Nederlands) (nl_NL) |  |
|
||||||
| English (English) (en_GB) |  |
|
| English (English) (en_GB) |  |
|
||||||
| English (US) (en_US) |  |
|
| English (US) (en_US) |  |
|
||||||
| French (Français) (fr_FR) |  |
|
| French (Français) (fr_FR) |  |
|
||||||
| German (Deutsch) (de_DE) |  |
|
| German (Deutsch) (de_DE) |  |
|
||||||
| Greek (Ελληνικά) (el_GR) |  |
|
| Greek (Ελληνικά) (el_GR) |  |
|
||||||
| Hindi (हिंदी) (hi_IN) |  |
|
| Hindi (हिंदी) (hi_IN) |  |
|
||||||
| Hungarian (Magyar) (hu_HU) |  |
|
| Hungarian (Magyar) (hu_HU) |  |
|
||||||
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
||||||
| Irish (Gaeilge) (ga_IE) |  |
|
| Irish (Gaeilge) (ga_IE) |  |
|
||||||
| Italian (Italiano) (it_IT) |  |
|
| Italian (Italiano) (it_IT) |  |
|
||||||
| Japanese (日本語) (ja_JP) |  |
|
| Japanese (日本語) (ja_JP) |  |
|
||||||
| Korean (한국어) (ko_KR) |  |
|
| Korean (한국어) (ko_KR) |  |
|
||||||
| Norwegian (Norsk) (no_NB) |  |
|
| Norwegian (Norsk) (no_NB) |  |
|
||||||
| Persian (فارسی) (fa_IR) |  |
|
| Persian (فارسی) (fa_IR) |  |
|
||||||
| Polish (Polski) (pl_PL) |  |
|
| Polish (Polski) (pl_PL) |  |
|
||||||
| Portuguese (Português) (pt_PT) |  |
|
| Portuguese (Português) (pt_PT) |  |
|
||||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||||
| Romanian (Română) (ro_RO) |  |
|
| Romanian (Română) (ro_RO) |  |
|
||||||
| Russian (Русский) (ru_RU) |  |
|
| Russian (Русский) (ru_RU) |  |
|
||||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||||
| Slovakian (Slovensky) (sk_SK) |  |
|
| Slovakian (Slovensky) (sk_SK) |  |
|
||||||
| Spanish (Español) (es_ES) |  |
|
| Spanish (Español) (es_ES) |  |
|
||||||
| Swedish (Svenska) (sv_SE) |  |
|
| Swedish (Svenska) (sv_SE) |  |
|
||||||
| Thai (ไทย) (th_TH) |  |
|
| Thai (ไทย) (th_TH) |  |
|
||||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||||
| Turkish (Türkçe) (tr_TR) |  |
|
| Turkish (Türkçe) (tr_TR) |  |
|
||||||
| Ukrainian (Українська) (uk_UA) |  |
|
| Ukrainian (Українська) (uk_UA) |  |
|
||||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||||
|
|
||||||
## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)
|
## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)
|
||||||
|
|
||||||
@ -405,7 +405,7 @@ To access your account settings, go to Account Settings in the settings cog menu
|
|||||||
|
|
||||||
To add new users, go to the bottom of Account Settings and hit 'Admin Settings'. Here you can add new users. The different roles mentioned within this are for rate limiting. This is a work in progress and will be expanded on more in the future.
|
To add new users, go to the bottom of Account Settings and hit 'Admin Settings'. Here you can add new users. The different roles mentioned within this are for rate limiting. This is a work in progress and will be expanded on more in the future.
|
||||||
|
|
||||||
For API usage, you must provide a header with `X-API-Key` and the associated API key for that user.
|
For API usage, you must provide a header with `X-API-KEY` and the associated API key for that user.
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
|
173
build.gradle
173
build.gradle
@ -8,6 +8,7 @@ plugins {
|
|||||||
id "com.diffplug.spotless" version "6.25.0"
|
id "com.diffplug.spotless" version "6.25.0"
|
||||||
id "com.github.jk1.dependency-license-report" version "2.9"
|
id "com.github.jk1.dependency-license-report" version "2.9"
|
||||||
//id "nebula.lint" version "19.0.3"
|
//id "nebula.lint" version "19.0.3"
|
||||||
|
id("org.panteleyev.jpackageplugin") version "1.6.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ ext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "stirling.software"
|
group = "stirling.software"
|
||||||
version = "0.36.0"
|
version = "0.36.1"
|
||||||
|
|
||||||
|
|
||||||
java {
|
java {
|
||||||
@ -41,6 +42,9 @@ repositories {
|
|||||||
maven {
|
maven {
|
||||||
url 'https://build.shibboleth.net/maven/releases'
|
url 'https://build.shibboleth.net/maven/releases'
|
||||||
}
|
}
|
||||||
|
maven { url "https://build.shibboleth.net/maven/releases" }
|
||||||
|
maven { url "https://maven.pkg.github.com/jcefmaven/jcefmaven" }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
licenseReport {
|
licenseReport {
|
||||||
@ -64,6 +68,12 @@ sourceSets {
|
|||||||
exclude "stirling/software/SPDF/model/User.java"
|
exclude "stirling/software/SPDF/model/User.java"
|
||||||
exclude "stirling/software/SPDF/repository/**"
|
exclude "stirling/software/SPDF/repository/**"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (System.getenv("STIRLING_PDF_DESKTOP_UI") == "false") {
|
||||||
|
exclude "stirling/software/SPDF/UI/impl/**"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,16 +84,153 @@ openApi {
|
|||||||
outputFileName = "SwaggerDoc.json"
|
outputFileName = "SwaggerDoc.json"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//0.11.5 to 2024.11.5
|
||||||
|
def getMacVersion(String version) {
|
||||||
|
def currentYear = java.time.Year.now().getValue()
|
||||||
|
def versionParts = version.split("\\.", 2)
|
||||||
|
return "${currentYear}.${versionParts.length > 1 ? versionParts[1] : versionParts[0]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
jpackage {
|
||||||
|
input = "build/libs"
|
||||||
|
|
||||||
|
appName = "Stirling-PDF"
|
||||||
|
appVersion = project.version
|
||||||
|
vendor = "Stirling-Software"
|
||||||
|
appDescription = "Stirling PDF - Your Local PDF Editor"
|
||||||
|
|
||||||
|
mainJar = "Stirling-PDF-${project.version}.jar"
|
||||||
|
mainClass = "org.springframework.boot.loader.launch.JarLauncher"
|
||||||
|
|
||||||
|
icon = "src/main/resources/static/favicon.ico"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// JVM Options
|
||||||
|
javaOptions = [
|
||||||
|
"-DBROWSER_OPEN=true",
|
||||||
|
"-DSTIRLING_PDF_DESKTOP_UI=true",
|
||||||
|
"-Djava.awt.headless=false",
|
||||||
|
"-Dapple.awt.UIElement=true",
|
||||||
|
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
|
||||||
|
"--add-opens", "java.desktop/java.awt.event=ALL-UNNAMED",
|
||||||
|
"--add-opens", "java.desktop/sun.awt=ALL-UNNAMED"
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
verbose = true
|
||||||
|
|
||||||
|
destination = "${projectDir}/build/jpackage"
|
||||||
|
|
||||||
|
// Windows-specific configuration
|
||||||
|
windows {
|
||||||
|
launcherAsService = false
|
||||||
|
appVersion = project.version
|
||||||
|
winConsole = false
|
||||||
|
winDirChooser = true
|
||||||
|
winMenu = true
|
||||||
|
winShortcut = true
|
||||||
|
winPerUserInstall = true
|
||||||
|
winMenuGroup = "Stirling Software"
|
||||||
|
winUpgradeUuid = "2a43ed0c-b8c2-40cf-89e1-751129b87641" // Unique identifier for updates
|
||||||
|
winHelpUrl = "https://github.com/Stirling-Tools/Stirling-PDF"
|
||||||
|
winUpdateUrl = "https://github.com/Stirling-Tools/Stirling-PDF/releases"
|
||||||
|
type = "exe"
|
||||||
|
installDir = "C:/Program Files/Stirling-PDF"
|
||||||
|
}
|
||||||
|
|
||||||
|
// macOS-specific configuration
|
||||||
|
mac {
|
||||||
|
appVersion = getMacVersion(project.version.toString())
|
||||||
|
icon = "src/main/resources/static/favicon.icns"
|
||||||
|
type = "dmg"
|
||||||
|
macPackageIdentifier = "com.stirling.software.pdf"
|
||||||
|
macPackageName = "Stirling-PDF"
|
||||||
|
macAppCategory = "public.app-category.productivity"
|
||||||
|
macSign = false // Enable signing
|
||||||
|
macAppStore = false // Not targeting App Store initially
|
||||||
|
|
||||||
|
//installDir = "Applications"
|
||||||
|
|
||||||
|
// Add license and other documentation to DMG
|
||||||
|
/*macDmgContent = [
|
||||||
|
"README.md",
|
||||||
|
"LICENSE",
|
||||||
|
"CHANGELOG.md"
|
||||||
|
]*/
|
||||||
|
|
||||||
|
// Enable Mac-specific entitlements
|
||||||
|
//macEntitlements = "entitlements.plist" // You'll need to create this file
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linux-specific configuration
|
||||||
|
linux {
|
||||||
|
appVersion = project.version
|
||||||
|
icon = "src/main/resources/static/favicon.png"
|
||||||
|
type = "deb" // Can also use "rpm" for Red Hat-based systems
|
||||||
|
|
||||||
|
// Debian package configuration
|
||||||
|
//linuxPackageName = "stirlingpdf"
|
||||||
|
linuxDebMaintainer = "support@stirlingpdf.com"
|
||||||
|
linuxMenuGroup = "Office;PDF;Productivity"
|
||||||
|
linuxAppCategory = "Office"
|
||||||
|
linuxAppRelease = "1"
|
||||||
|
linuxPackageDeps = true
|
||||||
|
|
||||||
|
installDir = "/opt/Stirling-PDF"
|
||||||
|
|
||||||
|
// RPM-specific settings
|
||||||
|
//linuxRpmLicenseType = "MIT"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common additional options
|
||||||
|
//jLinkOptions = [
|
||||||
|
// "--strip-debug",
|
||||||
|
// "--compress=2",
|
||||||
|
// "--no-header-files",
|
||||||
|
// "--no-man-pages"
|
||||||
|
//]
|
||||||
|
|
||||||
|
// Add any additional modules required
|
||||||
|
/*addModules = [
|
||||||
|
"java.base",
|
||||||
|
"java.desktop",
|
||||||
|
"java.logging",
|
||||||
|
"java.sql",
|
||||||
|
"java.xml",
|
||||||
|
"jdk.crypto.ec"
|
||||||
|
]*/
|
||||||
|
|
||||||
|
// Add copyright and license information
|
||||||
|
copyright = "Copyright © 2024 Stirling Software"
|
||||||
|
licenseFile = "LICENSE"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
launch4j {
|
launch4j {
|
||||||
icon = "${projectDir}/src/main/resources/static/favicon.ico"
|
icon = "${projectDir}/src/main/resources/static/favicon.ico"
|
||||||
|
|
||||||
outfile="Stirling-PDF.exe"
|
outfile="Stirling-PDF.exe"
|
||||||
headerType="console"
|
|
||||||
|
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
|
||||||
|
headerType = "gui"
|
||||||
|
} else {
|
||||||
|
headerType = "console"
|
||||||
|
}
|
||||||
jarTask = tasks.bootJar
|
jarTask = tasks.bootJar
|
||||||
|
|
||||||
errTitle="Encountered error, Do you have Java 21?"
|
errTitle="Encountered error, Do you have Java 21?"
|
||||||
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
|
downloadUrl="https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe"
|
||||||
variables=["BROWSER_OPEN=true"]
|
|
||||||
|
if(System.getenv("STIRLING_PDF_DESKTOP_UI") == 'true') {
|
||||||
|
variables=["BROWSER_OPEN=true", "STIRLING_PDF_DESKTOP_UI=true"]
|
||||||
|
} else {
|
||||||
|
variables=["BROWSER_OPEN=true"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
jreMinVersion="17"
|
jreMinVersion="17"
|
||||||
|
|
||||||
mutexName="Stirling-PDF"
|
mutexName="Stirling-PDF"
|
||||||
@ -123,6 +270,13 @@ configurations.all {
|
|||||||
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
|
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat"
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
|
if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") {
|
||||||
|
implementation "me.friwi:jcefmaven:127.3.1"
|
||||||
|
implementation "org.openjfx:javafx-controls:21"
|
||||||
|
implementation "org.openjfx:javafx-swing:21"
|
||||||
|
}
|
||||||
|
|
||||||
//security updates
|
//security updates
|
||||||
implementation "org.springframework:spring-webmvc:6.2.0"
|
implementation "org.springframework:spring-webmvc:6.2.0"
|
||||||
|
|
||||||
@ -142,7 +296,7 @@ dependencies {
|
|||||||
|
|
||||||
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
|
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
|
||||||
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
|
||||||
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE"
|
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.3.RELEASE"
|
||||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
|
||||||
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
|
||||||
|
|
||||||
@ -271,7 +425,14 @@ jar {
|
|||||||
tasks.named("test") {
|
tasks.named("test") {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
task printVersion {
|
task printVersion {
|
||||||
println project.version
|
doLast {
|
||||||
|
println project.version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task printMacVersion {
|
||||||
|
doLast {
|
||||||
|
println getMacVersion(project.version.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,10 @@ import shutil
|
|||||||
import re
|
import re
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw
|
||||||
|
|
||||||
|
API_HEADERS = {
|
||||||
|
'X-API-KEY': '123456789'
|
||||||
|
}
|
||||||
|
|
||||||
#########
|
#########
|
||||||
# GIVEN #
|
# GIVEN #
|
||||||
#########
|
#########
|
||||||
@ -227,7 +231,7 @@ def save_generated_pdf(context, filename):
|
|||||||
def step_send_get_request(context, endpoint):
|
def step_send_get_request(context, endpoint):
|
||||||
base_url = "http://localhost:8080"
|
base_url = "http://localhost:8080"
|
||||||
full_url = f"{base_url}{endpoint}"
|
full_url = f"{base_url}{endpoint}"
|
||||||
response = requests.get(full_url)
|
response = requests.get(full_url, headers=API_HEADERS)
|
||||||
context.response = response
|
context.response = response
|
||||||
|
|
||||||
@when('I send a GET request to "{endpoint}" with parameters')
|
@when('I send a GET request to "{endpoint}" with parameters')
|
||||||
@ -235,7 +239,7 @@ def step_send_get_request_with_params(context, endpoint):
|
|||||||
base_url = "http://localhost:8080"
|
base_url = "http://localhost:8080"
|
||||||
params = {row['parameter']: row['value'] for row in context.table}
|
params = {row['parameter']: row['value'] for row in context.table}
|
||||||
full_url = f"{base_url}{endpoint}"
|
full_url = f"{base_url}{endpoint}"
|
||||||
response = requests.get(full_url, params=params)
|
response = requests.get(full_url, params=params, headers=API_HEADERS)
|
||||||
context.response = response
|
context.response = response
|
||||||
|
|
||||||
@when('I send the API request to the endpoint "{endpoint}"')
|
@when('I send the API request to the endpoint "{endpoint}"')
|
||||||
@ -256,7 +260,7 @@ def step_send_api_request(context, endpoint):
|
|||||||
print(f"form_data {file.name} with {mime_type}")
|
print(f"form_data {file.name} with {mime_type}")
|
||||||
form_data.append((key, (file.name, file, mime_type)))
|
form_data.append((key, (file.name, file, mime_type)))
|
||||||
|
|
||||||
response = requests.post(url, files=form_data)
|
response = requests.post(url, files=form_data, headers=API_HEADERS)
|
||||||
context.response = response
|
context.response = response
|
||||||
|
|
||||||
########
|
########
|
||||||
|
34
exampleYmlFiles/test_cicd.yml
Normal file
34
exampleYmlFiles/test_cicd.yml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
services:
|
||||||
|
stirling-pdf:
|
||||||
|
container_name: Stirling-PDF-Security-Fat
|
||||||
|
image: stirlingtools/stirling-pdf:latest-fat
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 4G
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -f -H 'X-API-KEY: 123456789' http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 16
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
volumes:
|
||||||
|
- /stirling/latest/data:/usr/share/tessdata:rw
|
||||||
|
- /stirling/latest/config:/configs:rw
|
||||||
|
- /stirling/latest/logs:/logs:rw
|
||||||
|
environment:
|
||||||
|
DOCKER_ENABLE_SECURITY: "true"
|
||||||
|
SECURITY_ENABLELOGIN: "true"
|
||||||
|
PUID: 1002
|
||||||
|
PGID: 1002
|
||||||
|
UMASK: "022"
|
||||||
|
SYSTEM_DEFAULTLOCALE: en-US
|
||||||
|
UI_APPNAME: Stirling-PDF
|
||||||
|
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with Security
|
||||||
|
UI_APPNAMENAVBAR: Stirling-PDF Latest-fat
|
||||||
|
SYSTEM_MAXFILESIZE: "100"
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||||
|
SECURITY_CUSTOMGLOBALAPIKEY: "123456789"
|
||||||
|
restart: on-failure:5
|
@ -77,6 +77,11 @@ ignore = [
|
|||||||
'language.direction',
|
'language.direction',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[fa_IR]
|
||||||
|
ignore = [
|
||||||
|
'language.direction',
|
||||||
|
]
|
||||||
|
|
||||||
[fr_FR]
|
[fr_FR]
|
||||||
ignore = [
|
ignore = [
|
||||||
'AddStampRequest.alphabet',
|
'AddStampRequest.alphabet',
|
||||||
|
@ -28,7 +28,7 @@ public class LicenseKeyChecker {
|
|||||||
this.checkLicense();
|
this.checkLicense();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(initialDelay = 604800000,fixedRate = 604800000) // 7 days in milliseconds
|
@Scheduled(initialDelay = 604800000, fixedRate = 604800000) // 7 days in milliseconds
|
||||||
public void checkLicensePeriodically() {
|
public void checkLicensePeriodically() {
|
||||||
checkLicense();
|
checkLicense();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package stirling.software.SPDF;
|
package stirling.software.SPDF;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -8,6 +9,9 @@ import java.nio.file.Paths;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -19,13 +23,16 @@ import org.springframework.core.env.Environment;
|
|||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
import io.github.pixee.security.SystemCommand;
|
import io.github.pixee.security.SystemCommand;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import jakarta.annotation.PreDestroy;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.UI.WebBrowser;
|
||||||
import stirling.software.SPDF.config.ConfigInitializer;
|
import stirling.software.SPDF.config.ConfigInitializer;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
|
@Slf4j
|
||||||
public class SPdfApplication {
|
public class SPdfApplication {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class);
|
private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class);
|
||||||
@ -67,36 +74,19 @@ public class SPdfApplication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void init() {
|
|
||||||
baseUrlStatic = this.baseUrl;
|
|
||||||
// Check if the BROWSER_OPEN environment variable is set to true
|
|
||||||
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
|
|
||||||
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
|
||||||
if (browserOpen) {
|
|
||||||
try {
|
|
||||||
String url = baseUrl + ":" + getStaticPort();
|
|
||||||
|
|
||||||
String os = System.getProperty("os.name").toLowerCase();
|
|
||||||
Runtime rt = Runtime.getRuntime();
|
|
||||||
if (os.contains("win")) {
|
|
||||||
// For Windows
|
|
||||||
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
|
|
||||||
} else if (os.contains("mac")) {
|
|
||||||
SystemCommand.runCommand(rt, "open " + url);
|
|
||||||
} else if (os.contains("nix") || os.contains("nux")) {
|
|
||||||
SystemCommand.runCommand(rt, "xdg-open " + url);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Error opening browser: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.info("Running configs {}", applicationProperties.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException, InterruptedException {
|
public static void main(String[] args) throws IOException, InterruptedException {
|
||||||
|
|
||||||
SpringApplication app = new SpringApplication(SPdfApplication.class);
|
SpringApplication app = new SpringApplication(SPdfApplication.class);
|
||||||
|
|
||||||
|
Properties props = new Properties();
|
||||||
|
|
||||||
|
if (Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
||||||
|
System.setProperty("java.awt.headless", "false");
|
||||||
|
app.setHeadless(false);
|
||||||
|
props.put("java.awt.headless", "false");
|
||||||
|
props.put("spring.main.web-application-type", "servlet");
|
||||||
|
}
|
||||||
|
|
||||||
app.setAdditionalProfiles("default");
|
app.setAdditionalProfiles("default");
|
||||||
app.addInitializers(new ConfigInitializer());
|
app.addInitializers(new ConfigInitializer());
|
||||||
Map<String, String> propertyFiles = new HashMap<>();
|
Map<String, String> propertyFiles = new HashMap<>();
|
||||||
@ -120,14 +110,20 @@ public class SPdfApplication {
|
|||||||
} else {
|
} else {
|
||||||
logger.warn("Custom configuration file 'configs/custom_settings.yml' does not exist.");
|
logger.warn("Custom configuration file 'configs/custom_settings.yml' does not exist.");
|
||||||
}
|
}
|
||||||
|
Properties finalProps = new Properties();
|
||||||
|
|
||||||
if (!propertyFiles.isEmpty()) {
|
if (!propertyFiles.isEmpty()) {
|
||||||
app.setDefaultProperties(
|
finalProps.putAll(
|
||||||
Collections.singletonMap(
|
Collections.singletonMap(
|
||||||
"spring.config.additional-location",
|
"spring.config.additional-location",
|
||||||
propertyFiles.get("spring.config.additional-location")));
|
propertyFiles.get("spring.config.additional-location")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!props.isEmpty()) {
|
||||||
|
finalProps.putAll(props);
|
||||||
|
}
|
||||||
|
app.setDefaultProperties(finalProps);
|
||||||
|
|
||||||
app.run(args);
|
app.run(args);
|
||||||
|
|
||||||
// Ensure directories are created
|
// Ensure directories are created
|
||||||
@ -147,6 +143,46 @@ public class SPdfApplication {
|
|||||||
logger.info("Navigate to {}", url);
|
logger.info("Navigate to {}", url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private WebBrowser webBrowser;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
baseUrlStatic = this.baseUrl;
|
||||||
|
String url = baseUrl + ":" + getStaticPort();
|
||||||
|
if (webBrowser != null
|
||||||
|
&& Boolean.parseBoolean(System.getProperty("STIRLING_PDF_DESKTOP_UI", "false"))) {
|
||||||
|
webBrowser.initWebUI(url);
|
||||||
|
} else {
|
||||||
|
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
|
||||||
|
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
||||||
|
if (browserOpen) {
|
||||||
|
try {
|
||||||
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
|
Runtime rt = Runtime.getRuntime();
|
||||||
|
if (os.contains("win")) {
|
||||||
|
// For Windows
|
||||||
|
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
|
||||||
|
} else if (os.contains("mac")) {
|
||||||
|
SystemCommand.runCommand(rt, "open " + url);
|
||||||
|
} else if (os.contains("nix") || os.contains("nux")) {
|
||||||
|
SystemCommand.runCommand(rt, "xdg-open " + url);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error opening browser: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info("Running configs {}", applicationProperties.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void cleanup() {
|
||||||
|
if (webBrowser != null) {
|
||||||
|
webBrowser.cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static String getStaticBaseUrl() {
|
public static String getStaticBaseUrl() {
|
||||||
return baseUrlStatic;
|
return baseUrlStatic;
|
||||||
}
|
}
|
||||||
|
7
src/main/java/stirling/software/SPDF/UI/WebBrowser.java
Normal file
7
src/main/java/stirling/software/SPDF/UI/WebBrowser.java
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package stirling.software.SPDF.UI;
|
||||||
|
|
||||||
|
public interface WebBrowser {
|
||||||
|
void initWebUI(String url);
|
||||||
|
|
||||||
|
void cleanup();
|
||||||
|
}
|
354
src/main/java/stirling/software/SPDF/UI/impl/DesktopBrowser.java
Normal file
354
src/main/java/stirling/software/SPDF/UI/impl/DesktopBrowser.java
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
package stirling.software.SPDF.UI.impl;
|
||||||
|
|
||||||
|
import java.awt.AWTException;
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Frame;
|
||||||
|
import java.awt.Image;
|
||||||
|
import java.awt.MenuItem;
|
||||||
|
import java.awt.PopupMenu;
|
||||||
|
import java.awt.SystemTray;
|
||||||
|
import java.awt.TrayIcon;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.awt.event.WindowStateListener;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.Timer;
|
||||||
|
|
||||||
|
import org.cef.CefApp;
|
||||||
|
import org.cef.CefClient;
|
||||||
|
import org.cef.CefSettings;
|
||||||
|
import org.cef.browser.CefBrowser;
|
||||||
|
import org.cef.callback.CefBeforeDownloadCallback;
|
||||||
|
import org.cef.callback.CefDownloadItem;
|
||||||
|
import org.cef.callback.CefDownloadItemCallback;
|
||||||
|
import org.cef.handler.CefDownloadHandlerAdapter;
|
||||||
|
import org.cef.handler.CefLoadHandlerAdapter;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import jakarta.annotation.PreDestroy;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.friwi.jcefmaven.CefAppBuilder;
|
||||||
|
import me.friwi.jcefmaven.EnumProgress;
|
||||||
|
import me.friwi.jcefmaven.MavenCefAppHandlerAdapter;
|
||||||
|
import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler;
|
||||||
|
import stirling.software.SPDF.UI.WebBrowser;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
name = "STIRLING_PDF_DESKTOP_UI",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public class DesktopBrowser implements WebBrowser {
|
||||||
|
private static CefApp cefApp;
|
||||||
|
private static CefClient client;
|
||||||
|
private static CefBrowser browser;
|
||||||
|
private static JFrame frame;
|
||||||
|
private static LoadingWindow loadingWindow;
|
||||||
|
private static volatile boolean browserInitialized = false;
|
||||||
|
private static TrayIcon trayIcon;
|
||||||
|
private static SystemTray systemTray;
|
||||||
|
|
||||||
|
public DesktopBrowser() {
|
||||||
|
SwingUtilities.invokeLater(
|
||||||
|
() -> {
|
||||||
|
loadingWindow = new LoadingWindow(null, "Initializing...");
|
||||||
|
loadingWindow.setVisible(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initWebUI(String url) {
|
||||||
|
CompletableFuture.runAsync(
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
CefAppBuilder builder = new CefAppBuilder();
|
||||||
|
configureCefSettings(builder);
|
||||||
|
builder.setProgressHandler(createProgressHandler());
|
||||||
|
|
||||||
|
// Build and initialize CEF
|
||||||
|
cefApp = builder.build();
|
||||||
|
client = cefApp.createClient();
|
||||||
|
|
||||||
|
// Set up download handler
|
||||||
|
setupDownloadHandler();
|
||||||
|
|
||||||
|
// Create browser and frame on EDT
|
||||||
|
SwingUtilities.invokeAndWait(
|
||||||
|
() -> {
|
||||||
|
browser = client.createBrowser(url, false, false);
|
||||||
|
setupMainFrame();
|
||||||
|
setupLoadHandler();
|
||||||
|
|
||||||
|
// Show the frame immediately but transparent
|
||||||
|
frame.setVisible(true);
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error initializing JCEF browser: ", e);
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureCefSettings(CefAppBuilder builder) {
|
||||||
|
CefSettings settings = builder.getCefSettings();
|
||||||
|
settings.cache_path = new File("jcef-bundle").getAbsolutePath();
|
||||||
|
settings.root_cache_path = new File("jcef-bundle").getAbsolutePath();
|
||||||
|
settings.persist_session_cookies = true;
|
||||||
|
settings.windowless_rendering_enabled = false;
|
||||||
|
settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_INFO;
|
||||||
|
|
||||||
|
builder.setAppHandler(
|
||||||
|
new MavenCefAppHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void stateHasChanged(org.cef.CefApp.CefAppState state) {
|
||||||
|
log.info("CEF state changed: " + state);
|
||||||
|
if (state == CefApp.CefAppState.TERMINATED) {
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupDownloadHandler() {
|
||||||
|
client.addDownloadHandler(
|
||||||
|
new CefDownloadHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public boolean onBeforeDownload(
|
||||||
|
CefBrowser browser,
|
||||||
|
CefDownloadItem downloadItem,
|
||||||
|
String suggestedName,
|
||||||
|
CefBeforeDownloadCallback callback) {
|
||||||
|
callback.Continue("", true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDownloadUpdated(
|
||||||
|
CefBrowser browser,
|
||||||
|
CefDownloadItem downloadItem,
|
||||||
|
CefDownloadItemCallback callback) {
|
||||||
|
if (downloadItem.isComplete()) {
|
||||||
|
log.info("Download completed: " + downloadItem.getFullPath());
|
||||||
|
} else if (downloadItem.isCanceled()) {
|
||||||
|
log.info("Download canceled: " + downloadItem.getFullPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConsoleProgressHandler createProgressHandler() {
|
||||||
|
return new ConsoleProgressHandler() {
|
||||||
|
@Override
|
||||||
|
public void handleProgress(EnumProgress state, float percent) {
|
||||||
|
Objects.requireNonNull(state, "state cannot be null");
|
||||||
|
SwingUtilities.invokeLater(
|
||||||
|
() -> {
|
||||||
|
if (loadingWindow != null) {
|
||||||
|
switch (state) {
|
||||||
|
case LOCATING:
|
||||||
|
loadingWindow.setStatus("Locating Files...");
|
||||||
|
loadingWindow.setProgress(0);
|
||||||
|
break;
|
||||||
|
case DOWNLOADING:
|
||||||
|
if (percent >= 0) {
|
||||||
|
loadingWindow.setStatus(
|
||||||
|
String.format(
|
||||||
|
"Downloading additional files: %.0f%%",
|
||||||
|
percent));
|
||||||
|
loadingWindow.setProgress((int) percent);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EXTRACTING:
|
||||||
|
loadingWindow.setStatus("Extracting files...");
|
||||||
|
loadingWindow.setProgress(60);
|
||||||
|
break;
|
||||||
|
case INITIALIZING:
|
||||||
|
loadingWindow.setStatus("Initializing UI...");
|
||||||
|
loadingWindow.setProgress(80);
|
||||||
|
break;
|
||||||
|
case INITIALIZED:
|
||||||
|
loadingWindow.setStatus("Finalising startup...");
|
||||||
|
loadingWindow.setProgress(90);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupMainFrame() {
|
||||||
|
frame = new JFrame("Stirling-PDF");
|
||||||
|
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
|
||||||
|
frame.setUndecorated(true);
|
||||||
|
frame.setOpacity(0.0f);
|
||||||
|
|
||||||
|
JPanel contentPane = new JPanel(new BorderLayout());
|
||||||
|
contentPane.setDoubleBuffered(true);
|
||||||
|
contentPane.add(browser.getUIComponent(), BorderLayout.CENTER);
|
||||||
|
frame.setContentPane(contentPane);
|
||||||
|
|
||||||
|
frame.addWindowListener(
|
||||||
|
new java.awt.event.WindowAdapter() {
|
||||||
|
@Override
|
||||||
|
public void windowClosing(java.awt.event.WindowEvent windowEvent) {
|
||||||
|
cleanup();
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.setSize(1280, 768);
|
||||||
|
frame.setLocationRelativeTo(null);
|
||||||
|
|
||||||
|
loadIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupLoadHandler() {
|
||||||
|
client.addLoadHandler(
|
||||||
|
new CefLoadHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onLoadingStateChange(
|
||||||
|
CefBrowser browser,
|
||||||
|
boolean isLoading,
|
||||||
|
boolean canGoBack,
|
||||||
|
boolean canGoForward) {
|
||||||
|
if (!isLoading && !browserInitialized) {
|
||||||
|
browserInitialized = true;
|
||||||
|
SwingUtilities.invokeLater(
|
||||||
|
() -> {
|
||||||
|
if (loadingWindow != null) {
|
||||||
|
Timer timer =
|
||||||
|
new Timer(
|
||||||
|
500,
|
||||||
|
e -> {
|
||||||
|
loadingWindow.dispose();
|
||||||
|
loadingWindow = null;
|
||||||
|
|
||||||
|
frame.dispose();
|
||||||
|
frame.setOpacity(1.0f);
|
||||||
|
frame.setUndecorated(false);
|
||||||
|
frame.pack();
|
||||||
|
frame.setSize(1280, 800);
|
||||||
|
frame.setLocationRelativeTo(null);
|
||||||
|
frame.setVisible(true);
|
||||||
|
frame.requestFocus();
|
||||||
|
frame.toFront();
|
||||||
|
browser.getUIComponent()
|
||||||
|
.requestFocus();
|
||||||
|
});
|
||||||
|
timer.setRepeats(false);
|
||||||
|
timer.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupTrayIcon(Image icon) {
|
||||||
|
if (!SystemTray.isSupported()) {
|
||||||
|
log.warn("System tray is not supported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
systemTray = SystemTray.getSystemTray();
|
||||||
|
|
||||||
|
// Create popup menu
|
||||||
|
PopupMenu popup = new PopupMenu();
|
||||||
|
|
||||||
|
// Create menu items
|
||||||
|
MenuItem showItem = new MenuItem("Show");
|
||||||
|
showItem.addActionListener(
|
||||||
|
e -> {
|
||||||
|
frame.setVisible(true);
|
||||||
|
frame.setState(Frame.NORMAL);
|
||||||
|
});
|
||||||
|
|
||||||
|
MenuItem exitItem = new MenuItem("Exit");
|
||||||
|
exitItem.addActionListener(
|
||||||
|
e -> {
|
||||||
|
cleanup();
|
||||||
|
System.exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add menu items to popup menu
|
||||||
|
popup.add(showItem);
|
||||||
|
popup.addSeparator();
|
||||||
|
popup.add(exitItem);
|
||||||
|
|
||||||
|
// Create tray icon
|
||||||
|
trayIcon = new TrayIcon(icon, "Stirling-PDF", popup);
|
||||||
|
trayIcon.setImageAutoSize(true);
|
||||||
|
|
||||||
|
// Add double-click behavior
|
||||||
|
trayIcon.addActionListener(
|
||||||
|
e -> {
|
||||||
|
frame.setVisible(true);
|
||||||
|
frame.setState(Frame.NORMAL);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add tray icon to system tray
|
||||||
|
systemTray.add(trayIcon);
|
||||||
|
|
||||||
|
// Modify frame behavior to minimize to tray
|
||||||
|
frame.addWindowStateListener(
|
||||||
|
new WindowStateListener() {
|
||||||
|
public void windowStateChanged(WindowEvent e) {
|
||||||
|
if (e.getNewState() == Frame.ICONIFIED) {
|
||||||
|
frame.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (AWTException e) {
|
||||||
|
log.error("Error setting up system tray icon", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadIcon() {
|
||||||
|
try {
|
||||||
|
Image icon = null;
|
||||||
|
String[] iconPaths = {"/static/favicon.ico"};
|
||||||
|
|
||||||
|
for (String path : iconPaths) {
|
||||||
|
if (icon != null) break;
|
||||||
|
try {
|
||||||
|
try (InputStream is = getClass().getResourceAsStream(path)) {
|
||||||
|
if (is != null) {
|
||||||
|
icon = ImageIO.read(is);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("Could not load icon from " + path, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (icon != null) {
|
||||||
|
frame.setIconImage(icon);
|
||||||
|
setupTrayIcon(icon);
|
||||||
|
} else {
|
||||||
|
log.warn("Could not load icon from any source");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error loading icon", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void cleanup() {
|
||||||
|
if (browser != null) browser.close(true);
|
||||||
|
if (client != null) client.dispose();
|
||||||
|
if (cefApp != null) cefApp.dispose();
|
||||||
|
if (loadingWindow != null) loadingWindow.dispose();
|
||||||
|
}
|
||||||
|
}
|
114
src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java
Normal file
114
src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package stirling.software.SPDF.UI.impl;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class LoadingWindow extends JDialog {
|
||||||
|
private final JProgressBar progressBar;
|
||||||
|
private final JLabel statusLabel;
|
||||||
|
private final JPanel mainPanel;
|
||||||
|
private final JLabel brandLabel;
|
||||||
|
|
||||||
|
public LoadingWindow(Frame parent, String initialUrl) {
|
||||||
|
super(parent, "Initializing Stirling-PDF", true);
|
||||||
|
|
||||||
|
// Initialize components
|
||||||
|
mainPanel = new JPanel();
|
||||||
|
mainPanel.setBackground(Color.WHITE);
|
||||||
|
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 30, 20, 30));
|
||||||
|
mainPanel.setLayout(new GridBagLayout());
|
||||||
|
GridBagConstraints gbc = new GridBagConstraints();
|
||||||
|
|
||||||
|
// Configure GridBagConstraints
|
||||||
|
gbc.gridwidth = GridBagConstraints.REMAINDER;
|
||||||
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
gbc.insets = new Insets(5, 5, 5, 5);
|
||||||
|
gbc.weightx = 1.0; // Add horizontal weight
|
||||||
|
gbc.weighty = 0.0; // Add vertical weight
|
||||||
|
|
||||||
|
// Add icon
|
||||||
|
try {
|
||||||
|
try (InputStream is = getClass().getResourceAsStream("/static/favicon.ico")) {
|
||||||
|
if (is != null) {
|
||||||
|
Image img = ImageIO.read(is);
|
||||||
|
if (img != null) {
|
||||||
|
Image scaledImg = img.getScaledInstance(48, 48, Image.SCALE_SMOOTH);
|
||||||
|
JLabel iconLabel = new JLabel(new ImageIcon(scaledImg));
|
||||||
|
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
gbc.gridy = 0;
|
||||||
|
mainPanel.add(iconLabel, gbc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to load icon", e);
|
||||||
|
}
|
||||||
|
// URL Label with explicit size
|
||||||
|
brandLabel = new JLabel(initialUrl);
|
||||||
|
brandLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
brandLabel.setPreferredSize(new Dimension(300, 25));
|
||||||
|
brandLabel.setText("Stirling-PDF");
|
||||||
|
gbc.gridy = 1;
|
||||||
|
mainPanel.add(brandLabel, gbc);
|
||||||
|
|
||||||
|
// Status label with explicit size
|
||||||
|
statusLabel = new JLabel("Initializing...");
|
||||||
|
statusLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
statusLabel.setPreferredSize(new Dimension(300, 25));
|
||||||
|
gbc.gridy = 2;
|
||||||
|
mainPanel.add(statusLabel, gbc);
|
||||||
|
// Progress bar with explicit size
|
||||||
|
progressBar = new JProgressBar(0, 100);
|
||||||
|
progressBar.setStringPainted(true);
|
||||||
|
progressBar.setPreferredSize(new Dimension(300, 25));
|
||||||
|
gbc.gridy = 3;
|
||||||
|
mainPanel.add(progressBar, gbc);
|
||||||
|
|
||||||
|
// Set dialog properties
|
||||||
|
setContentPane(mainPanel);
|
||||||
|
setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
|
||||||
|
setResizable(false);
|
||||||
|
setUndecorated(false);
|
||||||
|
|
||||||
|
// Set size and position
|
||||||
|
setSize(400, 200);
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
setAlwaysOnTop(true);
|
||||||
|
setProgress(0);
|
||||||
|
setStatus("Starting...");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProgress(final int progress) {
|
||||||
|
SwingUtilities.invokeLater(
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
progressBar.setValue(Math.min(Math.max(progress, 0), 100));
|
||||||
|
progressBar.setString(progress + "%");
|
||||||
|
mainPanel.revalidate();
|
||||||
|
mainPanel.repaint();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error updating progress", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(final String status) {
|
||||||
|
log.info(status);
|
||||||
|
SwingUtilities.invokeLater(
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
statusLabel.setText(status != null ? status : "");
|
||||||
|
mainPanel.revalidate();
|
||||||
|
mainPanel.repaint();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error updating status", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -260,6 +260,9 @@ public class EndpointConfiguration {
|
|||||||
|
|
||||||
// Pdftohtml dependent endpoints
|
// Pdftohtml dependent endpoints
|
||||||
addEndpointToGroup("Pdftohtml", "pdf-to-html");
|
addEndpointToGroup("Pdftohtml", "pdf-to-html");
|
||||||
|
|
||||||
|
// disabled for now while we resolve issues
|
||||||
|
disableEndpoint("pdf-to-pdfa");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processEnvironmentConfigs() {
|
private void processEnvironmentConfigs() {
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Properties;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import io.micrometer.common.util.StringUtils;
|
import io.micrometer.common.util.StringUtils;
|
||||||
@ -23,6 +26,18 @@ public class InitialSetup {
|
|||||||
@Autowired private ApplicationProperties applicationProperties;
|
@Autowired private ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
|
public void init() throws IOException {
|
||||||
|
initUUIDKey();
|
||||||
|
|
||||||
|
initSecretKey();
|
||||||
|
|
||||||
|
initEnableCSRFSecurity();
|
||||||
|
|
||||||
|
initLegalUrls();
|
||||||
|
|
||||||
|
initSetAppVersion();
|
||||||
|
}
|
||||||
|
|
||||||
public void initUUIDKey() throws IOException {
|
public void initUUIDKey() throws IOException {
|
||||||
String uuid = applicationProperties.getAutomaticallyGenerated().getUUID();
|
String uuid = applicationProperties.getAutomaticallyGenerated().getUUID();
|
||||||
if (!GeneralUtils.isValidUUID(uuid)) {
|
if (!GeneralUtils.isValidUUID(uuid)) {
|
||||||
@ -32,7 +47,6 @@ public class InitialSetup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void initSecretKey() throws IOException {
|
public void initSecretKey() throws IOException {
|
||||||
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
|
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
|
||||||
if (!GeneralUtils.isValidUUID(secretKey)) {
|
if (!GeneralUtils.isValidUUID(secretKey)) {
|
||||||
@ -42,13 +56,24 @@ public class InitialSetup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
public void initEnableCSRFSecurity() throws IOException {
|
||||||
|
if (GeneralUtils.isVersionHigher(
|
||||||
|
"0.36.0", applicationProperties.getAutomaticallyGenerated().getAppVersion())) {
|
||||||
|
Boolean csrf = applicationProperties.getSecurity().getCsrfDisabled();
|
||||||
|
if (!csrf) {
|
||||||
|
GeneralUtils.saveKeyToConfig("security.csrfDisabled", false, false);
|
||||||
|
GeneralUtils.saveKeyToConfig("system.enableAnalytics", "true", false);
|
||||||
|
applicationProperties.getSecurity().setCsrfDisabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void initLegalUrls() throws IOException {
|
public void initLegalUrls() throws IOException {
|
||||||
// Initialize Terms and Conditions
|
// Initialize Terms and Conditions
|
||||||
String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
|
String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
|
||||||
if (StringUtils.isEmpty(termsUrl)) {
|
if (StringUtils.isEmpty(termsUrl)) {
|
||||||
String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions";
|
String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions";
|
||||||
GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl);
|
GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl, false);
|
||||||
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
|
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,8 +81,23 @@ public class InitialSetup {
|
|||||||
String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy();
|
String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy();
|
||||||
if (StringUtils.isEmpty(privacyUrl)) {
|
if (StringUtils.isEmpty(privacyUrl)) {
|
||||||
String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy";
|
String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy";
|
||||||
GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl);
|
GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl, false);
|
||||||
applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl);
|
applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void initSetAppVersion() throws IOException {
|
||||||
|
|
||||||
|
String appVersion = "0.0.0";
|
||||||
|
Resource resource = new ClassPathResource("version.properties");
|
||||||
|
Properties props = new Properties();
|
||||||
|
try {
|
||||||
|
props.load(resource.getInputStream());
|
||||||
|
appVersion = props.getProperty("version");
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
applicationProperties.getAutomaticallyGenerated().setAppVersion(appVersion);
|
||||||
|
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.appVersion", appVersion, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,5 +75,6 @@ public class InitialSecuritySetup {
|
|||||||
userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
|
userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
|
||||||
log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
|
log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
|
||||||
}
|
}
|
||||||
|
userService.syncCustomApiUser(applicationProperties.getSecurity().getCustomGlobalAPIKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ public class SecurityConfiguration {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
if (applicationProperties.getSecurity().getCsrfDisabled()) {
|
if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) {
|
||||||
http.csrf(csrf -> csrf.disable());
|
http.csrf(csrf -> csrf.disable());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ public class SecurityConfiguration {
|
|||||||
csrf ->
|
csrf ->
|
||||||
csrf.ignoringRequestMatchers(
|
csrf.ignoringRequestMatchers(
|
||||||
request -> {
|
request -> {
|
||||||
String apiKey = request.getHeader("X-API-Key");
|
String apiKey = request.getHeader("X-API-KEY");
|
||||||
|
|
||||||
// If there's no API key, don't ignore CSRF
|
// If there's no API key, don't ignore CSRF
|
||||||
// (return false)
|
// (return false)
|
||||||
@ -289,17 +289,17 @@ public class SecurityConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (!applicationProperties.getSecurity().getCsrfDisabled()) {
|
// if (!applicationProperties.getSecurity().getCsrfDisabled()) {
|
||||||
CookieCsrfTokenRepository cookieRepo =
|
// CookieCsrfTokenRepository cookieRepo =
|
||||||
CookieCsrfTokenRepository.withHttpOnlyFalse();
|
// CookieCsrfTokenRepository.withHttpOnlyFalse();
|
||||||
CsrfTokenRequestAttributeHandler requestHandler =
|
// CsrfTokenRequestAttributeHandler requestHandler =
|
||||||
new CsrfTokenRequestAttributeHandler();
|
// new CsrfTokenRequestAttributeHandler();
|
||||||
requestHandler.setCsrfRequestAttributeName(null);
|
// requestHandler.setCsrfRequestAttributeName(null);
|
||||||
http.csrf(
|
// http.csrf(
|
||||||
csrf ->
|
// csrf ->
|
||||||
csrf.csrfTokenRepository(cookieRepo)
|
// csrf.csrfTokenRepository(cookieRepo)
|
||||||
.csrfTokenRequestHandler(requestHandler));
|
// .csrfTokenRequestHandler(requestHandler));
|
||||||
}
|
// }
|
||||||
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
|
|
||||||
// Check for API key in the request headers if no authentication exists
|
// Check for API key in the request headers if no authentication exists
|
||||||
if (authentication == null || !authentication.isAuthenticated()) {
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
String apiKey = request.getHeader("X-API-Key");
|
String apiKey = request.getHeader("X-API-KEY");
|
||||||
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
||||||
try {
|
try {
|
||||||
// Use API key to authenticate. This requires you to have an authentication
|
// Use API key to authenticate. This requires you to have an authentication
|
||||||
|
@ -59,7 +59,7 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
|||||||
String identifier = null;
|
String identifier = null;
|
||||||
|
|
||||||
// Check for API key in the request headers
|
// Check for API key in the request headers
|
||||||
String apiKey = request.getHeader("X-API-Key");
|
String apiKey = request.getHeader("X-API-KEY");
|
||||||
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
if (apiKey != null && !apiKey.trim().isEmpty()) {
|
||||||
identifier =
|
identifier =
|
||||||
"API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
|
"API_KEY_" + apiKey; // Prefix to distinguish between API keys and usernames
|
||||||
@ -79,7 +79,7 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
|
|||||||
Role userRole =
|
Role userRole =
|
||||||
getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
|
getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());
|
||||||
|
|
||||||
if (request.getHeader("X-API-Key") != null) {
|
if (request.getHeader("X-API-KEY") != null) {
|
||||||
// It's an API call
|
// It's an API call
|
||||||
processRequest(
|
processRequest(
|
||||||
userRole.getApiCallsPerDay(),
|
userRole.getApiCallsPerDay(),
|
||||||
|
@ -390,6 +390,37 @@ public class UserService implements UserServiceInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void syncCustomApiUser(String customApiKey) throws IOException {
|
||||||
|
if (customApiKey == null || customApiKey.trim().length() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String username = "CUSTOM_API_USER";
|
||||||
|
Optional<User> existingUser = findByUsernameIgnoreCase(username);
|
||||||
|
|
||||||
|
if (!existingUser.isPresent()) {
|
||||||
|
// Create new user with API role
|
||||||
|
User user = new User();
|
||||||
|
user.setUsername(username);
|
||||||
|
user.setPassword(UUID.randomUUID().toString());
|
||||||
|
user.setEnabled(true);
|
||||||
|
user.setFirstLogin(false);
|
||||||
|
user.setAuthenticationType(AuthenticationType.WEB);
|
||||||
|
user.setApiKey(customApiKey);
|
||||||
|
user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user));
|
||||||
|
userRepository.save(user);
|
||||||
|
databaseBackupHelper.exportDatabase();
|
||||||
|
} else {
|
||||||
|
// Update API key if it has changed
|
||||||
|
User user = existingUser.get();
|
||||||
|
if (!customApiKey.equals(user.getApiKey())) {
|
||||||
|
user.setApiKey(customApiKey);
|
||||||
|
userRepository.save(user);
|
||||||
|
databaseBackupHelper.exportDatabase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getTotalUsersCount() {
|
public long getTotalUsersCount() {
|
||||||
return userRepository.count();
|
return userRepository.count();
|
||||||
|
@ -52,84 +52,115 @@ public class SplitPDFController {
|
|||||||
"This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO")
|
"This endpoint splits a given PDF file into separate documents based on the specified page numbers or ranges. Users can specify pages using individual numbers, ranges, or 'all' for every page. Input:PDF Output:PDF Type:SIMO")
|
||||||
public ResponseEntity<byte[]> splitPdf(@ModelAttribute PDFWithPageNums request)
|
public ResponseEntity<byte[]> splitPdf(@ModelAttribute PDFWithPageNums request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
MultipartFile file = request.getFileInput();
|
|
||||||
String pages = request.getPageNumbers();
|
|
||||||
// open the pdf document
|
|
||||||
|
|
||||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
PDDocument document = null;
|
||||||
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
|
Path zipFile = null;
|
||||||
int totalPages = document.getNumberOfPages();
|
|
||||||
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
|
|
||||||
if (!pageNumbers.contains(totalPages - 1)) {
|
|
||||||
// Create a mutable ArrayList so we can add to it
|
|
||||||
pageNumbers = new ArrayList<>(pageNumbers);
|
|
||||||
pageNumbers.add(totalPages - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
"Splitting PDF into pages: {}",
|
|
||||||
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
|
||||||
|
|
||||||
// split the document
|
|
||||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<>();
|
||||||
int previousPageNumber = 0;
|
|
||||||
for (int splitPoint : pageNumbers) {
|
try {
|
||||||
try (PDDocument splitDocument =
|
|
||||||
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) {
|
MultipartFile file = request.getFileInput();
|
||||||
for (int i = previousPageNumber; i <= splitPoint; i++) {
|
String pages = request.getPageNumbers();
|
||||||
PDPage page = document.getPage(i);
|
// open the pdf document
|
||||||
splitDocument.addPage(page);
|
|
||||||
logger.info("Adding page {} to split document", i);
|
document = Loader.loadPDF(file.getBytes());
|
||||||
|
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
|
||||||
|
int totalPages = document.getNumberOfPages();
|
||||||
|
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
|
||||||
|
if (!pageNumbers.contains(totalPages - 1)) {
|
||||||
|
// Create a mutable ArrayList so we can add to it
|
||||||
|
pageNumbers = new ArrayList<>(pageNumbers);
|
||||||
|
pageNumbers.add(totalPages - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"Splitting PDF into pages: {}",
|
||||||
|
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||||||
|
|
||||||
|
// split the document
|
||||||
|
splitDocumentsBoas = new ArrayList<>();
|
||||||
|
int previousPageNumber = 0;
|
||||||
|
for (int splitPoint : pageNumbers) {
|
||||||
|
try (PDDocument splitDocument =
|
||||||
|
pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) {
|
||||||
|
for (int i = previousPageNumber; i <= splitPoint; i++) {
|
||||||
|
PDPage page = document.getPage(i);
|
||||||
|
splitDocument.addPage(page);
|
||||||
|
logger.info("Adding page {} to split document", i);
|
||||||
|
}
|
||||||
|
previousPageNumber = splitPoint + 1;
|
||||||
|
|
||||||
|
// Transfer metadata to split pdf
|
||||||
|
// PdfMetadataService.setMetadataToPdf(splitDocument, metadata);
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
splitDocument.save(baos);
|
||||||
|
|
||||||
|
splitDocumentsBoas.add(baos);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed splitting documents and saving them", e);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
previousPageNumber = splitPoint + 1;
|
}
|
||||||
|
|
||||||
// Transfer metadata to split pdf
|
// closing the original document
|
||||||
// PdfMetadataService.setMetadataToPdf(splitDocument, metadata);
|
document.close();
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
zipFile = Files.createTempFile("split_documents", ".zip");
|
||||||
splitDocument.save(baos);
|
|
||||||
|
|
||||||
splitDocumentsBoas.add(baos);
|
String filename =
|
||||||
|
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||||
|
.replaceFirst("[.][^.]+$", "");
|
||||||
|
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
|
||||||
|
// loop through the split documents and write them to the zip file
|
||||||
|
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
|
||||||
|
String fileName = filename + "_" + (i + 1) + ".pdf";
|
||||||
|
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
||||||
|
byte[] pdf = baos.toByteArray();
|
||||||
|
|
||||||
|
// Add PDF file to the zip
|
||||||
|
ZipEntry pdfEntry = new ZipEntry(fileName);
|
||||||
|
zipOut.putNextEntry(pdfEntry);
|
||||||
|
zipOut.write(pdf);
|
||||||
|
zipOut.closeEntry();
|
||||||
|
|
||||||
|
logger.info("Wrote split document {} to zip file", fileName);
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Failed splitting documents and saving them", e);
|
logger.error("Failed writing to zip", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// closing the original document
|
logger.info(
|
||||||
document.close();
|
"Successfully created zip file with split documents: {}", zipFile.toString());
|
||||||
|
byte[] data = Files.readAllBytes(zipFile);
|
||||||
|
Files.deleteIfExists(zipFile);
|
||||||
|
|
||||||
Path zipFile = Files.createTempFile("split_documents", ".zip");
|
// return the Resource in the response
|
||||||
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
|
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
|
||||||
String filename =
|
} finally {
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
try {
|
||||||
.replaceFirst("[.][^.]+$", "");
|
// Close the main document
|
||||||
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
|
if (document != null) {
|
||||||
// loop through the split documents and write them to the zip file
|
document.close();
|
||||||
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
|
}
|
||||||
String fileName = filename + "_" + (i + 1) + ".pdf";
|
|
||||||
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
|
||||||
byte[] pdf = baos.toByteArray();
|
|
||||||
|
|
||||||
// Add PDF file to the zip
|
// Close all ByteArrayOutputStreams
|
||||||
ZipEntry pdfEntry = new ZipEntry(fileName);
|
for (ByteArrayOutputStream baos : splitDocumentsBoas) {
|
||||||
zipOut.putNextEntry(pdfEntry);
|
if (baos != null) {
|
||||||
zipOut.write(pdf);
|
baos.close();
|
||||||
zipOut.closeEntry();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.info("Wrote split document {} to zip file", fileName);
|
// Delete temporary zip file
|
||||||
|
if (zipFile != null) {
|
||||||
|
Files.deleteIfExists(zipFile);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error while cleaning up resources", e);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Failed writing to zip", e);
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
|
|
||||||
byte[] data = Files.readAllBytes(zipFile);
|
|
||||||
Files.deleteIfExists(zipFile);
|
|
||||||
|
|
||||||
// return the Resource in the response
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
|
||||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,70 +59,86 @@ public class SplitPdfByChaptersController {
|
|||||||
public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfByChaptersRequest request)
|
public ResponseEntity<byte[]> splitPdf(@ModelAttribute SplitPdfByChaptersRequest request)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
boolean includeMetadata = request.getIncludeMetadata();
|
PDDocument sourceDocument = null;
|
||||||
Integer bookmarkLevel =
|
Path zipFile = null;
|
||||||
request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
|
|
||||||
if (bookmarkLevel < 0) {
|
|
||||||
return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes());
|
|
||||||
}
|
|
||||||
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
|
||||||
|
|
||||||
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
|
|
||||||
|
|
||||||
if (outline == null) {
|
|
||||||
logger.warn("No outline found for {}", file.getOriginalFilename());
|
|
||||||
return ResponseEntity.badRequest().body("No outline found".getBytes());
|
|
||||||
}
|
|
||||||
List<Bookmark> bookmarks = new ArrayList<>();
|
|
||||||
try {
|
try {
|
||||||
bookmarks =
|
boolean includeMetadata = request.getIncludeMetadata();
|
||||||
extractOutlineItems(
|
Integer bookmarkLevel =
|
||||||
sourceDocument,
|
request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)
|
||||||
outline.getFirstChild(),
|
if (bookmarkLevel < 0) {
|
||||||
bookmarks,
|
return ResponseEntity.badRequest().body("Invalid bookmark level".getBytes());
|
||||||
outline.getFirstChild().getNextSibling(),
|
}
|
||||||
0,
|
sourceDocument = Loader.loadPDF(file.getBytes());
|
||||||
bookmarkLevel);
|
|
||||||
// to handle last page edge case
|
|
||||||
bookmarks.get(bookmarks.size() - 1).setEndPage(sourceDocument.getNumberOfPages());
|
|
||||||
Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
|
||||||
logger.error("Unable to extract outline items", e);
|
|
||||||
return ResponseEntity.internalServerError()
|
if (outline == null) {
|
||||||
.body("Unable to extract outline items".getBytes());
|
logger.warn("No outline found for {}", file.getOriginalFilename());
|
||||||
|
return ResponseEntity.badRequest().body("No outline found".getBytes());
|
||||||
|
}
|
||||||
|
List<Bookmark> bookmarks = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
bookmarks =
|
||||||
|
extractOutlineItems(
|
||||||
|
sourceDocument,
|
||||||
|
outline.getFirstChild(),
|
||||||
|
bookmarks,
|
||||||
|
outline.getFirstChild().getNextSibling(),
|
||||||
|
0,
|
||||||
|
bookmarkLevel);
|
||||||
|
// to handle last page edge case
|
||||||
|
bookmarks.get(bookmarks.size() - 1).setEndPage(sourceDocument.getNumberOfPages());
|
||||||
|
Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Unable to extract outline items", e);
|
||||||
|
return ResponseEntity.internalServerError()
|
||||||
|
.body("Unable to extract outline items".getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean allowDuplicates = request.getAllowDuplicates();
|
||||||
|
if (!allowDuplicates) {
|
||||||
|
/*
|
||||||
|
duplicates are generated when multiple bookmarks correspond to the same page,
|
||||||
|
if the user doesn't want duplicates mergeBookmarksThatCorrespondToSamePage() method will merge the titles of all
|
||||||
|
the bookmarks that correspond to the same page, and treat them as a single bookmark
|
||||||
|
*/
|
||||||
|
bookmarks = mergeBookmarksThatCorrespondToSamePage(bookmarks);
|
||||||
|
}
|
||||||
|
for (Bookmark bookmark : bookmarks) {
|
||||||
|
logger.info(
|
||||||
|
"{}::::{} to {}",
|
||||||
|
bookmark.getTitle(),
|
||||||
|
bookmark.getStartPage(),
|
||||||
|
bookmark.getEndPage());
|
||||||
|
}
|
||||||
|
List<ByteArrayOutputStream> splitDocumentsBoas =
|
||||||
|
getSplitDocumentsBoas(sourceDocument, bookmarks, includeMetadata);
|
||||||
|
|
||||||
|
zipFile = createZipFile(bookmarks, splitDocumentsBoas);
|
||||||
|
|
||||||
|
byte[] data = Files.readAllBytes(zipFile);
|
||||||
|
Files.deleteIfExists(zipFile);
|
||||||
|
|
||||||
|
String filename =
|
||||||
|
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||||
|
.replaceFirst("[.][^.]+$", "");
|
||||||
|
sourceDocument.close();
|
||||||
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
|
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (sourceDocument != null) {
|
||||||
|
sourceDocument.close();
|
||||||
|
}
|
||||||
|
if (zipFile != null) {
|
||||||
|
Files.deleteIfExists(zipFile);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error while cleaning up resources", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean allowDuplicates = request.getAllowDuplicates();
|
|
||||||
if (!allowDuplicates) {
|
|
||||||
/*
|
|
||||||
duplicates are generated when multiple bookmarks correspond to the same page,
|
|
||||||
if the user doesn't want duplicates mergeBookmarksThatCorrespondToSamePage() method will merge the titles of all
|
|
||||||
the bookmarks that correspond to the same page, and treat them as a single bookmark
|
|
||||||
*/
|
|
||||||
bookmarks = mergeBookmarksThatCorrespondToSamePage(bookmarks);
|
|
||||||
}
|
|
||||||
for (Bookmark bookmark : bookmarks) {
|
|
||||||
logger.info(
|
|
||||||
"{}::::{} to {}",
|
|
||||||
bookmark.getTitle(),
|
|
||||||
bookmark.getStartPage(),
|
|
||||||
bookmark.getEndPage());
|
|
||||||
}
|
|
||||||
List<ByteArrayOutputStream> splitDocumentsBoas =
|
|
||||||
getSplitDocumentsBoas(sourceDocument, bookmarks, includeMetadata);
|
|
||||||
|
|
||||||
Path zipFile = createZipFile(bookmarks, splitDocumentsBoas);
|
|
||||||
|
|
||||||
byte[] data = Files.readAllBytes(zipFile);
|
|
||||||
Files.deleteIfExists(zipFile);
|
|
||||||
|
|
||||||
String filename =
|
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "");
|
|
||||||
sourceDocument.close();
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
|
||||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Bookmark> mergeBookmarksThatCorrespondToSamePage(List<Bookmark> bookmarks) {
|
private List<Bookmark> mergeBookmarksThatCorrespondToSamePage(List<Bookmark> bookmarks) {
|
||||||
|
@ -105,15 +105,13 @@ public class SplitPdfBySectionsController {
|
|||||||
|
|
||||||
if (sectionNum == horiz * verti) pageNum++;
|
if (sectionNum == horiz * verti) pageNum++;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("exception", e);
|
|
||||||
} finally {
|
|
||||||
data = Files.readAllBytes(zipFile);
|
data = Files.readAllBytes(zipFile);
|
||||||
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
|
data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
|
||||||
|
} finally {
|
||||||
Files.deleteIfExists(zipFile);
|
Files.deleteIfExists(zipFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
|
||||||
data, filename + "_split.zip", MediaType.APPLICATION_OCTET_STREAM);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PDDocument> splitPdfPages(
|
public List<PDDocument> splitPdfPages(
|
||||||
|
@ -65,112 +65,137 @@ public class ConvertImgPDFController {
|
|||||||
String colorType = request.getColorType();
|
String colorType = request.getColorType();
|
||||||
String dpi = request.getDpi();
|
String dpi = request.getDpi();
|
||||||
|
|
||||||
byte[] pdfBytes = file.getBytes();
|
Path tempFile = null;
|
||||||
ImageType colorTypeResult = ImageType.RGB;
|
Path tempOutputDir = null;
|
||||||
if ("greyscale".equals(colorType)) {
|
Path tempPdfPath = null;
|
||||||
colorTypeResult = ImageType.GRAY;
|
|
||||||
} else if ("blackwhite".equals(colorType)) {
|
|
||||||
colorTypeResult = ImageType.BINARY;
|
|
||||||
}
|
|
||||||
// returns bytes for image
|
|
||||||
boolean singleImage = "single".equals(singleOrMultiple);
|
|
||||||
byte[] result = null;
|
byte[] result = null;
|
||||||
String filename =
|
|
||||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "");
|
|
||||||
|
|
||||||
result =
|
try {
|
||||||
PdfUtils.convertFromPdf(
|
byte[] pdfBytes = file.getBytes();
|
||||||
pdfBytes,
|
ImageType colorTypeResult = ImageType.RGB;
|
||||||
"webp".equalsIgnoreCase(imageFormat) ? "png" : imageFormat.toUpperCase(),
|
if ("greyscale".equals(colorType)) {
|
||||||
colorTypeResult,
|
colorTypeResult = ImageType.GRAY;
|
||||||
singleImage,
|
} else if ("blackwhite".equals(colorType)) {
|
||||||
Integer.valueOf(dpi),
|
colorTypeResult = ImageType.BINARY;
|
||||||
filename);
|
|
||||||
if (result == null || result.length == 0) {
|
|
||||||
logger.error("resultant bytes for {} is null, error converting ", filename);
|
|
||||||
}
|
|
||||||
if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) {
|
|
||||||
throw new IOException("Python is not installed. Required for WebP conversion.");
|
|
||||||
} else if ("webp".equalsIgnoreCase(imageFormat)
|
|
||||||
&& CheckProgramInstall.isPythonAvailable()) {
|
|
||||||
// Write the output stream to a temp file
|
|
||||||
Path tempFile = Files.createTempFile("temp_png", ".png");
|
|
||||||
try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {
|
|
||||||
fos.write(result);
|
|
||||||
fos.flush();
|
|
||||||
}
|
}
|
||||||
|
// returns bytes for image
|
||||||
|
boolean singleImage = "single".equals(singleOrMultiple);
|
||||||
|
String filename =
|
||||||
|
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||||
|
.replaceFirst("[.][^.]+$", "");
|
||||||
|
|
||||||
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
|
result =
|
||||||
|
PdfUtils.convertFromPdf(
|
||||||
List<String> command = new ArrayList<>();
|
pdfBytes,
|
||||||
command.add(pythonVersion);
|
"webp".equalsIgnoreCase(imageFormat)
|
||||||
command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion
|
? "png"
|
||||||
|
: imageFormat.toUpperCase(),
|
||||||
// Create a temporary directory for the output WebP files
|
colorTypeResult,
|
||||||
Path tempOutputDir = Files.createTempDirectory("webp_output");
|
singleImage,
|
||||||
if (singleImage) {
|
Integer.valueOf(dpi),
|
||||||
// Run the Python script to convert PNG to WebP
|
filename);
|
||||||
command.add(tempFile.toString());
|
if (result == null || result.length == 0) {
|
||||||
command.add(tempOutputDir.toString());
|
logger.error("resultant bytes for {} is null, error converting ", filename);
|
||||||
command.add("--single");
|
|
||||||
} else {
|
|
||||||
// Save the uploaded PDF to a temporary file
|
|
||||||
Path tempPdfPath = Files.createTempFile("temp_pdf", ".pdf");
|
|
||||||
file.transferTo(tempPdfPath.toFile());
|
|
||||||
// Run the Python script to convert PDF to WebP
|
|
||||||
command.add(tempPdfPath.toString());
|
|
||||||
command.add(tempOutputDir.toString());
|
|
||||||
}
|
}
|
||||||
command.add("--dpi");
|
if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) {
|
||||||
command.add(dpi);
|
throw new IOException("Python is not installed. Required for WebP conversion.");
|
||||||
ProcessExecutorResult resultProcess =
|
} else if ("webp".equalsIgnoreCase(imageFormat)
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
|
&& CheckProgramInstall.isPythonAvailable()) {
|
||||||
.runCommandWithOutputHandling(command);
|
// Write the output stream to a temp file
|
||||||
|
tempFile = Files.createTempFile("temp_png", ".png");
|
||||||
// Find all WebP files in the output directory
|
try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {
|
||||||
List<Path> webpFiles =
|
fos.write(result);
|
||||||
Files.walk(tempOutputDir)
|
fos.flush();
|
||||||
.filter(path -> path.toString().endsWith(".webp"))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (webpFiles.isEmpty()) {
|
|
||||||
logger.error("No WebP files were created in: {}", tempOutputDir.toString());
|
|
||||||
throw new IOException("No WebP files were created. " + resultProcess.getMessages());
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] bodyBytes = new byte[0];
|
|
||||||
|
|
||||||
if (webpFiles.size() == 1) {
|
|
||||||
// Return the single WebP file directly
|
|
||||||
Path webpFilePath = webpFiles.get(0);
|
|
||||||
bodyBytes = Files.readAllBytes(webpFilePath);
|
|
||||||
} else {
|
|
||||||
// Create a ZIP file containing all WebP images
|
|
||||||
ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream();
|
|
||||||
try (ZipOutputStream zos = new ZipOutputStream(zipOutputStream)) {
|
|
||||||
for (Path webpFile : webpFiles) {
|
|
||||||
zos.putNextEntry(new ZipEntry(webpFile.getFileName().toString()));
|
|
||||||
Files.copy(webpFile, zos);
|
|
||||||
zos.closeEntry();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
bodyBytes = zipOutputStream.toByteArray();
|
|
||||||
}
|
|
||||||
// Clean up the temporary files
|
|
||||||
Files.deleteIfExists(tempFile);
|
|
||||||
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
|
||||||
result = bodyBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (singleImage) {
|
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
|
||||||
String docName = filename + "." + imageFormat;
|
|
||||||
MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat));
|
List<String> command = new ArrayList<>();
|
||||||
return WebResponseUtils.bytesToWebResponse(result, docName, mediaType);
|
command.add(pythonVersion);
|
||||||
} else {
|
command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion
|
||||||
String zipFilename = filename + "_convertedToImages.zip";
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
// Create a temporary directory for the output WebP files
|
||||||
result, zipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
tempOutputDir = Files.createTempDirectory("webp_output");
|
||||||
|
if (singleImage) {
|
||||||
|
// Run the Python script to convert PNG to WebP
|
||||||
|
command.add(tempFile.toString());
|
||||||
|
command.add(tempOutputDir.toString());
|
||||||
|
command.add("--single");
|
||||||
|
} else {
|
||||||
|
// Save the uploaded PDF to a temporary file
|
||||||
|
tempPdfPath = Files.createTempFile("temp_pdf", ".pdf");
|
||||||
|
file.transferTo(tempPdfPath.toFile());
|
||||||
|
// Run the Python script to convert PDF to WebP
|
||||||
|
command.add(tempPdfPath.toString());
|
||||||
|
command.add(tempOutputDir.toString());
|
||||||
|
}
|
||||||
|
command.add("--dpi");
|
||||||
|
command.add(dpi);
|
||||||
|
ProcessExecutorResult resultProcess =
|
||||||
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)
|
||||||
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
|
// Find all WebP files in the output directory
|
||||||
|
List<Path> webpFiles =
|
||||||
|
Files.walk(tempOutputDir)
|
||||||
|
.filter(path -> path.toString().endsWith(".webp"))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (webpFiles.isEmpty()) {
|
||||||
|
logger.error("No WebP files were created in: {}", tempOutputDir.toString());
|
||||||
|
throw new IOException(
|
||||||
|
"No WebP files were created. " + resultProcess.getMessages());
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bodyBytes = new byte[0];
|
||||||
|
|
||||||
|
if (webpFiles.size() == 1) {
|
||||||
|
// Return the single WebP file directly
|
||||||
|
Path webpFilePath = webpFiles.get(0);
|
||||||
|
bodyBytes = Files.readAllBytes(webpFilePath);
|
||||||
|
} else {
|
||||||
|
// Create a ZIP file containing all WebP images
|
||||||
|
ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream();
|
||||||
|
try (ZipOutputStream zos = new ZipOutputStream(zipOutputStream)) {
|
||||||
|
for (Path webpFile : webpFiles) {
|
||||||
|
zos.putNextEntry(new ZipEntry(webpFile.getFileName().toString()));
|
||||||
|
Files.copy(webpFile, zos);
|
||||||
|
zos.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bodyBytes = zipOutputStream.toByteArray();
|
||||||
|
}
|
||||||
|
// Clean up the temporary files
|
||||||
|
Files.deleteIfExists(tempFile);
|
||||||
|
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||||
|
result = bodyBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (singleImage) {
|
||||||
|
String docName = filename + "." + imageFormat;
|
||||||
|
MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat));
|
||||||
|
return WebResponseUtils.bytesToWebResponse(result, docName, mediaType);
|
||||||
|
} else {
|
||||||
|
String zipFilename = filename + "_convertedToImages.zip";
|
||||||
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
|
result, zipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
// Clean up temporary files
|
||||||
|
if (tempFile != null) {
|
||||||
|
Files.deleteIfExists(tempFile);
|
||||||
|
}
|
||||||
|
if (tempPdfPath != null) {
|
||||||
|
Files.deleteIfExists(tempPdfPath);
|
||||||
|
}
|
||||||
|
if (tempOutputDir != null) {
|
||||||
|
FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Error cleaning up temporary files", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ public class OCRController {
|
|||||||
|
|
||||||
Files.createDirectories(tempOutputDir);
|
Files.createDirectories(tempOutputDir);
|
||||||
Files.createDirectories(tempImagesDir);
|
Files.createDirectories(tempImagesDir);
|
||||||
|
Process process = null;
|
||||||
try {
|
try {
|
||||||
// Save input file
|
// Save input file
|
||||||
inputFile.transferTo(tempInputFile.toFile());
|
inputFile.transferTo(tempInputFile.toFile());
|
||||||
@ -139,7 +139,7 @@ public class OCRController {
|
|||||||
command.add("pdf"); // Always output PDF
|
command.add("pdf"); // Always output PDF
|
||||||
|
|
||||||
ProcessBuilder pb = new ProcessBuilder(command);
|
ProcessBuilder pb = new ProcessBuilder(command);
|
||||||
Process process = pb.start();
|
process = pb.start();
|
||||||
|
|
||||||
// Capture any error output
|
// Capture any error output
|
||||||
try (BufferedReader reader =
|
try (BufferedReader reader =
|
||||||
@ -188,6 +188,10 @@ public class OCRController {
|
|||||||
.body(pdfContent);
|
.body(pdfContent);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
|
if (process != null) {
|
||||||
|
process.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up temporary files
|
// Clean up temporary files
|
||||||
deleteDirectory(tempDir);
|
deleteDirectory(tempDir);
|
||||||
}
|
}
|
||||||
|
@ -221,7 +221,7 @@ public class PipelineProcessor {
|
|||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
String apiKey = getApiKeyForUser();
|
String apiKey = getApiKeyForUser();
|
||||||
headers.add("X-API-Key", apiKey);
|
headers.add("X-API-KEY", apiKey);
|
||||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||||
|
|
||||||
// Create HttpEntity with the body and headers
|
// Create HttpEntity with the body and headers
|
||||||
|
@ -595,7 +595,9 @@ public class GetInfoOnPDF {
|
|||||||
|
|
||||||
permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument()));
|
permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument()));
|
||||||
permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent()));
|
permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent()));
|
||||||
permissionsNode.put("Extracting for accessibility", getPermissionState(ap.canExtractForAccessibility()));
|
permissionsNode.put(
|
||||||
|
"Extracting for accessibility",
|
||||||
|
getPermissionState(ap.canExtractForAccessibility()));
|
||||||
permissionsNode.put("Form Filling", getPermissionState(ap.canFillInForm()));
|
permissionsNode.put("Form Filling", getPermissionState(ap.canFillInForm()));
|
||||||
permissionsNode.put("Modifying", getPermissionState(ap.canModify()));
|
permissionsNode.put("Modifying", getPermissionState(ap.canModify()));
|
||||||
permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations()));
|
permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations()));
|
||||||
|
@ -92,20 +92,29 @@ public class ValidateSignatureController {
|
|||||||
SignerInformationStore signerStore = signedData.getSignerInfos();
|
SignerInformationStore signerStore = signedData.getSignerInfos();
|
||||||
|
|
||||||
for (SignerInformation signer : signerStore.getSigners()) {
|
for (SignerInformation signer : signerStore.getSigners()) {
|
||||||
X509CertificateHolder certHolder = (X509CertificateHolder) certStore.getMatches(signer.getSID()).iterator().next();
|
X509CertificateHolder certHolder =
|
||||||
X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certHolder);
|
(X509CertificateHolder)
|
||||||
|
certStore.getMatches(signer.getSID()).iterator().next();
|
||||||
|
X509Certificate cert =
|
||||||
|
new JcaX509CertificateConverter().getCertificate(certHolder);
|
||||||
|
|
||||||
boolean isValid = signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
|
boolean isValid =
|
||||||
|
signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
|
||||||
result.setValid(isValid);
|
result.setValid(isValid);
|
||||||
|
|
||||||
// Additional validations
|
// Additional validations
|
||||||
result.setChainValid(customCert != null
|
result.setChainValid(
|
||||||
? certValidationService.validateCertificateChainWithCustomCert(cert, customCert)
|
customCert != null
|
||||||
: certValidationService.validateCertificateChain(cert));
|
? certValidationService
|
||||||
|
.validateCertificateChainWithCustomCert(
|
||||||
|
cert, customCert)
|
||||||
|
: certValidationService.validateCertificateChain(cert));
|
||||||
|
|
||||||
result.setTrustValid(customCert != null
|
result.setTrustValid(
|
||||||
? certValidationService.validateTrustWithCustomCert(cert, customCert)
|
customCert != null
|
||||||
: certValidationService.validateTrustStore(cert));
|
? certValidationService.validateTrustWithCustomCert(
|
||||||
|
cert, customCert)
|
||||||
|
: certValidationService.validateTrustStore(cert));
|
||||||
|
|
||||||
result.setNotRevoked(!certValidationService.isRevoked(cert));
|
result.setNotRevoked(!certValidationService.isRevoked(cert));
|
||||||
result.setNotExpired(!cert.getNotAfter().before(new Date()));
|
result.setNotExpired(!cert.getNotAfter().before(new Date()));
|
||||||
@ -126,7 +135,8 @@ public class ValidateSignatureController {
|
|||||||
|
|
||||||
// Get key size (if possible)
|
// Get key size (if possible)
|
||||||
try {
|
try {
|
||||||
result.setKeySize(((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength());
|
result.setKeySize(
|
||||||
|
((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// If not RSA or error, set to 0
|
// If not RSA or error, set to 0
|
||||||
result.setKeySize(0);
|
result.setKeySize(0);
|
||||||
@ -152,7 +162,9 @@ public class ValidateSignatureController {
|
|||||||
result.setKeyUsages(keyUsages);
|
result.setKeyUsages(keyUsages);
|
||||||
|
|
||||||
// Check if self-signed
|
// Check if self-signed
|
||||||
result.setSelfSigned(cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal()));
|
result.setSelfSigned(
|
||||||
|
cert.getSubjectX500Principal()
|
||||||
|
.equals(cert.getIssuerX500Principal()));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
result.setValid(false);
|
result.setValid(false);
|
||||||
|
@ -73,6 +73,7 @@ public class ApplicationProperties {
|
|||||||
private int loginAttemptCount;
|
private int loginAttemptCount;
|
||||||
private long loginResetTimeMinutes;
|
private long loginResetTimeMinutes;
|
||||||
private String loginMethod = "all";
|
private String loginMethod = "all";
|
||||||
|
private String customGlobalAPIKey;
|
||||||
|
|
||||||
public Boolean isAltLogin() {
|
public Boolean isAltLogin() {
|
||||||
return saml2.getEnabled() || oauth2.getEnabled();
|
return saml2.getEnabled() || oauth2.getEnabled();
|
||||||
@ -285,6 +286,7 @@ public class ApplicationProperties {
|
|||||||
public static class AutomaticallyGenerated {
|
public static class AutomaticallyGenerated {
|
||||||
@ToString.Exclude private String key;
|
@ToString.Exclude private String key;
|
||||||
private String UUID;
|
private String UUID;
|
||||||
|
private String appVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
@ -17,15 +17,14 @@ public class SignatureValidationResult {
|
|||||||
private boolean notExpired;
|
private boolean notExpired;
|
||||||
private boolean notRevoked;
|
private boolean notRevoked;
|
||||||
|
|
||||||
private String issuerDN; // Certificate issuer's Distinguished Name
|
private String issuerDN; // Certificate issuer's Distinguished Name
|
||||||
private String subjectDN; // Certificate subject's Distinguished Name
|
private String subjectDN; // Certificate subject's Distinguished Name
|
||||||
private String serialNumber; // Certificate serial number
|
private String serialNumber; // Certificate serial number
|
||||||
private String validFrom; // Certificate validity start date
|
private String validFrom; // Certificate validity start date
|
||||||
private String validUntil; // Certificate validity end date
|
private String validUntil; // Certificate validity end date
|
||||||
private String signatureAlgorithm;// Algorithm used for signing
|
private String signatureAlgorithm; // Algorithm used for signing
|
||||||
private int keySize; // Key size in bits
|
private int keySize; // Key size in bits
|
||||||
private String version; // Certificate version
|
private String version; // Certificate version
|
||||||
private List<String> keyUsages; // List of key usage purposes
|
private List<String> keyUsages; // List of key usage purposes
|
||||||
private boolean isSelfSigned; // Whether the certificate is self-signed
|
private boolean isSelfSigned; // Whether the certificate is self-signed
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package stirling.software.SPDF.service;
|
package stirling.software.SPDF.service;
|
||||||
|
|
||||||
import io.github.pixee.security.BoundedLineReader;
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@ -24,6 +23,8 @@ import java.util.Set;
|
|||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import io.github.pixee.security.BoundedLineReader;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@ -88,15 +88,45 @@ public class GeneralUtils {
|
|||||||
|
|
||||||
public static boolean isURLReachable(String urlStr) {
|
public static boolean isURLReachable(String urlStr) {
|
||||||
try {
|
try {
|
||||||
|
// Parse the URL
|
||||||
URL url = URI.create(urlStr).toURL();
|
URL url = URI.create(urlStr).toURL();
|
||||||
|
|
||||||
|
// Allow only http and https protocols
|
||||||
|
String protocol = url.getProtocol();
|
||||||
|
if (!protocol.equals("http") && !protocol.equals("https")) {
|
||||||
|
return false; // Disallow other protocols
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the host is a local address
|
||||||
|
String host = url.getHost();
|
||||||
|
if (isLocalAddress(host)) {
|
||||||
|
return false; // Exclude local addresses
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the URL is reachable
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
connection.setRequestMethod("HEAD");
|
connection.setRequestMethod("HEAD");
|
||||||
|
// connection.setConnectTimeout(5000); // Set connection timeout
|
||||||
|
// connection.setReadTimeout(5000); // Set read timeout
|
||||||
int responseCode = connection.getResponseCode();
|
int responseCode = connection.getResponseCode();
|
||||||
return (200 <= responseCode && responseCode <= 399);
|
return (200 <= responseCode && responseCode <= 399);
|
||||||
} catch (MalformedURLException e) {
|
} catch (Exception e) {
|
||||||
return false;
|
return false; // Return false in case of any exception
|
||||||
} catch (IOException e) {
|
}
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
private static boolean isLocalAddress(String host) {
|
||||||
|
try {
|
||||||
|
// Resolve DNS to IP address
|
||||||
|
InetAddress address = InetAddress.getByName(host);
|
||||||
|
|
||||||
|
// Check for local addresses
|
||||||
|
return address.isAnyLocalAddress() || // Matches 0.0.0.0 or similar
|
||||||
|
address.isLoopbackAddress() || // Matches 127.0.0.1 or ::1
|
||||||
|
address.isSiteLocalAddress() || // Matches private IPv4 ranges: 192.168.x.x, 10.x.x.x, 172.16.x.x to 172.31.x.x
|
||||||
|
address.getHostAddress().startsWith("fe80:"); // Matches link-local IPv6 addresses
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false; // Return false for invalid or unresolved addresses
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,6 +319,10 @@ public class GeneralUtils {
|
|||||||
saveKeyToConfig(id, key, true);
|
saveKeyToConfig(id, key, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void saveKeyToConfig(String id, boolean key) throws IOException {
|
||||||
|
saveKeyToConfig(id, key, true);
|
||||||
|
}
|
||||||
|
|
||||||
public static void saveKeyToConfig(String id, String key, boolean autoGenerated)
|
public static void saveKeyToConfig(String id, String key, boolean autoGenerated)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
|
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
|
||||||
@ -307,6 +341,24 @@ public class GeneralUtils {
|
|||||||
settingsYml.save();
|
settingsYml.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void saveKeyToConfig(String id, boolean key, boolean autoGenerated)
|
||||||
|
throws IOException {
|
||||||
|
Path path = Paths.get("configs", "settings.yml");
|
||||||
|
|
||||||
|
final YamlFile settingsYml = new YamlFile(path.toFile());
|
||||||
|
DumperOptions yamlOptionssettingsYml =
|
||||||
|
((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions();
|
||||||
|
yamlOptionssettingsYml.setSplitLines(false);
|
||||||
|
|
||||||
|
settingsYml.loadWithComments();
|
||||||
|
|
||||||
|
YamlFileWrapper writer = settingsYml.path(id).set(key);
|
||||||
|
if (autoGenerated) {
|
||||||
|
writer.comment("# Automatically Generated Settings (Do Not Edit Directly)");
|
||||||
|
}
|
||||||
|
settingsYml.save();
|
||||||
|
}
|
||||||
|
|
||||||
public static String generateMachineFingerprint() {
|
public static String generateMachineFingerprint() {
|
||||||
try {
|
try {
|
||||||
// Get the MAC address
|
// Get the MAC address
|
||||||
@ -349,4 +401,33 @@ public class GeneralUtils {
|
|||||||
return "GenericID";
|
return "GenericID";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isVersionHigher(String currentVersion, String compareVersion) {
|
||||||
|
if (currentVersion == null || compareVersion == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split versions into components
|
||||||
|
String[] current = currentVersion.split("\\.");
|
||||||
|
String[] compare = compareVersion.split("\\.");
|
||||||
|
|
||||||
|
// Get the length of the shorter version array
|
||||||
|
int length = Math.min(current.length, compare.length);
|
||||||
|
|
||||||
|
// Compare each component
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
int currentPart = Integer.parseInt(current[i]);
|
||||||
|
int comparePart = Integer.parseInt(compare[i]);
|
||||||
|
|
||||||
|
if (currentPart > comparePart) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (currentPart < comparePart) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all components so far are equal, the longer version is considered higher
|
||||||
|
return current.length > compare.length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=الصفحات المحددة
|
|||||||
multiTool.undo=تراجع
|
multiTool.undo=تراجع
|
||||||
multiTool.redo=إعادة إجراء
|
multiTool.redo=إعادة إجراء
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=هذه الميزة متوفرة في <a href="{0}">صفحة الأدوات المتعددة</a> لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية!
|
multiTool-advert.message=هذه الميزة متوفرة في <a href="{0}">صفحة الأدوات المتعددة</a> لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Seçilmiş Səhifə(lər)
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=Bu xüsusiyyət bizim <a href="{0}">multi-alət səhifə</a>mizdə də mövcuddur. Əlavə xüsusiyyətlər və səhifə-səhifə interfeys üçün sınaqdan keçirin!
|
multiTool-advert.message=Bu xüsusiyyət bizim <a href="{0}">multi-alət səhifə</a>mizdə də mövcuddur. Əlavə xüsusiyyətlər və səhifə-səhifə interfeys üçün sınaqdan keçirin!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Ausgewählte Seite(n)
|
|||||||
multiTool.undo=Rückgängig machen
|
multiTool.undo=Rückgängig machen
|
||||||
multiTool.redo=Wiederherstellen
|
multiTool.redo=Wiederherstellen
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=Diese Funktion ist auch auf unserer <a href="{0}">PDF-Multitool-Seite</a> verfügbar. Probieren Sie sie aus, denn sie bietet eine verbesserte Benutzeroberfläche und zusätzliche Funktionen!
|
multiTool-advert.message=Diese Funktion ist auch auf unserer <a href="{0}">PDF-Multitool-Seite</a> verfügbar. Probieren Sie sie aus, denn sie bietet eine verbesserte Benutzeroberfläche und zusätzliche Funktionen!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -122,6 +122,7 @@ enterpriseEdition.warning=این ویژگی فقط برای کاربران حر
|
|||||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro از فایلهای پیکربندی YAML و دیگر ویژگیهای SSO پشتیبانی میکند.
|
enterpriseEdition.yamlAdvert=Stirling PDF Pro از فایلهای پیکربندی YAML و دیگر ویژگیهای SSO پشتیبانی میکند.
|
||||||
enterpriseEdition.ssoAdvert=به دنبال ویژگیهای بیشتر برای مدیریت کاربران هستید؟ Stirling PDF Pro را بررسی کنید
|
enterpriseEdition.ssoAdvert=به دنبال ویژگیهای بیشتر برای مدیریت کاربران هستید؟ Stirling PDF Pro را بررسی کنید
|
||||||
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Analytics #
|
# Analytics #
|
||||||
#################
|
#################
|
||||||
@ -515,6 +516,7 @@ home.validateSignature.title=اعتبارسنجی امضای PDF
|
|||||||
home.validateSignature.desc=تأیید امضاها و گواهیهای دیجیتال در اسناد PDF
|
home.validateSignature.desc=تأیید امضاها و گواهیهای دیجیتال در اسناد PDF
|
||||||
validateSignature.tags=امضا، تأیید، اعتبارسنجی، PDF، گواهینامه، امضای دیجیتال
|
validateSignature.tags=امضا، تأیید، اعتبارسنجی، PDF، گواهینامه، امضای دیجیتال
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
replace-color.title=جایگزینی/معکوس کردن رنگ
|
replace-color.title=جایگزینی/معکوس کردن رنگ
|
||||||
replace-color.header=جایگزینی/معکوس کردن رنگ PDF
|
replace-color.header=جایگزینی/معکوس کردن رنگ PDF
|
||||||
home.replaceColorPdf.title=جایگزینی و معکوس کردن رنگ
|
home.replaceColorPdf.title=جایگزینی و معکوس کردن رنگ
|
||||||
@ -535,7 +537,6 @@ replace-color.submit=جایگزینی
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
# WEB PAGES #
|
# WEB PAGES #
|
||||||
@ -611,6 +612,7 @@ MarkdownToPDF.help=در حال پیشرفت
|
|||||||
MarkdownToPDF.credit=از WeasyPrint استفاده میکند
|
MarkdownToPDF.credit=از WeasyPrint استفاده میکند
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#url-to-pdf
|
#url-to-pdf
|
||||||
URLToPDF.title=URL به PDF
|
URLToPDF.title=URL به PDF
|
||||||
URLToPDF.header=URL به PDF
|
URLToPDF.header=URL به PDF
|
||||||
@ -903,7 +905,7 @@ compress.selectText.5=اندازه PDF مورد انتظار (مثلاً ۲۵MB
|
|||||||
compress.submit=فشردهسازی
|
compress.submit=فشردهسازی
|
||||||
|
|
||||||
|
|
||||||
# Add image
|
#Add image
|
||||||
addImage.title=افزودن تصویر
|
addImage.title=افزودن تصویر
|
||||||
addImage.header=افزودن تصویر به PDF
|
addImage.header=افزودن تصویر به PDF
|
||||||
addImage.everyPage=هر صفحه؟
|
addImage.everyPage=هر صفحه؟
|
||||||
@ -911,7 +913,7 @@ addImage.upload=افزودن تصویر
|
|||||||
addImage.submit=افزودن تصویر
|
addImage.submit=افزودن تصویر
|
||||||
|
|
||||||
|
|
||||||
# Merge
|
#merge
|
||||||
merge.title=ادغام
|
merge.title=ادغام
|
||||||
merge.header=ادغام چندین PDF (۲+)
|
merge.header=ادغام چندین PDF (۲+)
|
||||||
merge.sortByName=مرتبسازی بر اساس نام
|
merge.sortByName=مرتبسازی بر اساس نام
|
||||||
@ -920,7 +922,7 @@ merge.removeCertSign=حذف امضای دیجیتال در فایل ادغام
|
|||||||
merge.submit=ادغام
|
merge.submit=ادغام
|
||||||
|
|
||||||
|
|
||||||
# PDF Organizer
|
#pdfOrganiser
|
||||||
pdfOrganiser.title=سازماندهی صفحات
|
pdfOrganiser.title=سازماندهی صفحات
|
||||||
pdfOrganiser.header=سازماندهی صفحات PDF
|
pdfOrganiser.header=سازماندهی صفحات PDF
|
||||||
pdfOrganiser.submit=بازآرایی صفحات
|
pdfOrganiser.submit=بازآرایی صفحات
|
||||||
@ -938,7 +940,7 @@ pdfOrganiser.mode.10=ادغام فرد-زوج
|
|||||||
pdfOrganiser.placeholder=(مثال: ۱,۳,۲ یا ۴-۸,۲,۱۰-۱۲ یا 2n-1)
|
pdfOrganiser.placeholder=(مثال: ۱,۳,۲ یا ۴-۸,۲,۱۰-۱۲ یا 2n-1)
|
||||||
|
|
||||||
|
|
||||||
# Multi Tool
|
#multiTool
|
||||||
multiTool.title=ابزار چندگانه PDF
|
multiTool.title=ابزار چندگانه PDF
|
||||||
multiTool.header=ابزار چندگانه PDF
|
multiTool.header=ابزار چندگانه PDF
|
||||||
multiTool.uploadPrompts=نام فایل
|
multiTool.uploadPrompts=نام فایل
|
||||||
@ -963,14 +965,24 @@ multiTool.dragDropMessage=صفحه(ها) انتخاب شدهاند
|
|||||||
multiTool.undo=واگرد
|
multiTool.undo=واگرد
|
||||||
multiTool.redo=بازگرداندن
|
multiTool.redo=بازگرداندن
|
||||||
|
|
||||||
# Multi Tool Advert
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
|
#multiTool-advert
|
||||||
multiTool-advert.message=این ویژگی همچنین در <a href="{0}">صفحه ابزار چندگانه ما</a> موجود است. برای رابط کاربری صفحه به صفحه پیشرفته و ویژگیهای اضافی بررسی کنید!
|
multiTool-advert.message=این ویژگی همچنین در <a href="{0}">صفحه ابزار چندگانه ما</a> موجود است. برای رابط کاربری صفحه به صفحه پیشرفته و ویژگیهای اضافی بررسی کنید!
|
||||||
|
|
||||||
# View PDF
|
#view pdf
|
||||||
viewPdf.title=مشاهده PDF
|
viewPdf.title=مشاهده PDF
|
||||||
viewPdf.header=مشاهده PDF
|
viewPdf.header=مشاهده PDF
|
||||||
|
|
||||||
# Page Remover
|
#pageRemover
|
||||||
pageRemover.title=حذف صفحات
|
pageRemover.title=حذف صفحات
|
||||||
pageRemover.header=حذف صفحات PDF
|
pageRemover.header=حذف صفحات PDF
|
||||||
pageRemover.pagesToDelete=صفحات برای حذف (یک لیست از اعداد صفحه جدا شده با کاما وارد کنید):
|
pageRemover.pagesToDelete=صفحات برای حذف (یک لیست از اعداد صفحه جدا شده با کاما وارد کنید):
|
||||||
@ -978,14 +990,14 @@ pageRemover.submit=حذف صفحات
|
|||||||
pageRemover.placeholder=(مثال: ۱,۲,۶ یا ۱-۱۰,۱۵-۳۰)
|
pageRemover.placeholder=(مثال: ۱,۲,۶ یا ۱-۱۰,۱۵-۳۰)
|
||||||
|
|
||||||
|
|
||||||
# Rotate
|
#rotate
|
||||||
rotate.title=چرخش PDF
|
rotate.title=چرخش PDF
|
||||||
rotate.header=چرخش PDF
|
rotate.header=چرخش PDF
|
||||||
rotate.selectAngle=زاویه چرخش را انتخاب کنید (به مضربهای ۹۰ درجه):
|
rotate.selectAngle=زاویه چرخش را انتخاب کنید (به مضربهای ۹۰ درجه):
|
||||||
rotate.submit=چرخش
|
rotate.submit=چرخش
|
||||||
|
|
||||||
|
|
||||||
# Split PDFs
|
#split-pdfs
|
||||||
split.title=تقسیم PDF
|
split.title=تقسیم PDF
|
||||||
split.header=تقسیم PDF
|
split.header=تقسیم PDF
|
||||||
split.desc.1=اعدادی که انتخاب میکنید شماره صفحههایی هستند که میخواهید بر روی آنها تقسیم انجام دهید
|
split.desc.1=اعدادی که انتخاب میکنید شماره صفحههایی هستند که میخواهید بر روی آنها تقسیم انجام دهید
|
||||||
@ -1014,7 +1026,7 @@ imageToPDF.selectText.4=ادغام در یک PDF واحد
|
|||||||
imageToPDF.selectText.5=تبدیل به PDF های جداگانه
|
imageToPDF.selectText.5=تبدیل به PDF های جداگانه
|
||||||
|
|
||||||
|
|
||||||
# PDF to Image
|
#pdfToImage
|
||||||
pdfToImage.title=PDF به تصویر
|
pdfToImage.title=PDF به تصویر
|
||||||
pdfToImage.header=PDF به تصویر
|
pdfToImage.header=PDF به تصویر
|
||||||
pdfToImage.selectText=فرمت تصویر
|
pdfToImage.selectText=فرمت تصویر
|
||||||
@ -1029,7 +1041,7 @@ pdfToImage.submit=تبدیل
|
|||||||
pdfToImage.info=پایتون نصب نشده است. برای تبدیل WebP لازم است.
|
pdfToImage.info=پایتون نصب نشده است. برای تبدیل WebP لازم است.
|
||||||
|
|
||||||
|
|
||||||
# Add Password
|
#addPassword
|
||||||
addPassword.title=افزودن گذرواژه
|
addPassword.title=افزودن گذرواژه
|
||||||
addPassword.header=افزودن گذرواژه (رمزنگاری)
|
addPassword.header=افزودن گذرواژه (رمزنگاری)
|
||||||
addPassword.selectText.1=انتخاب PDF برای رمزنگاری
|
addPassword.selectText.1=انتخاب PDF برای رمزنگاری
|
||||||
@ -1051,7 +1063,7 @@ addPassword.selectText.16=محدودیتهای باز شدن خود سند
|
|||||||
addPassword.submit=رمزنگاری
|
addPassword.submit=رمزنگاری
|
||||||
|
|
||||||
|
|
||||||
# Watermark
|
#watermark
|
||||||
watermark.title=افزودن واترمارک
|
watermark.title=افزودن واترمارک
|
||||||
watermark.header=افزودن واترمارک
|
watermark.header=افزودن واترمارک
|
||||||
watermark.customColor=رنگ متن سفارشی
|
watermark.customColor=رنگ متن سفارشی
|
||||||
@ -1070,7 +1082,7 @@ watermark.type.1=متن
|
|||||||
watermark.type.2=تصویر
|
watermark.type.2=تصویر
|
||||||
|
|
||||||
|
|
||||||
# Change Permissions
|
#Change permissions
|
||||||
permissions.title=تغییر مجوزها
|
permissions.title=تغییر مجوزها
|
||||||
permissions.header=تغییر مجوزها
|
permissions.header=تغییر مجوزها
|
||||||
permissions.warning=برای اینکه این مجوزها غیرقابل تغییر باشند، توصیه میشود آنها را با گذرواژه از طریق صفحه افزودن گذرواژه تنظیم کنید
|
permissions.warning=برای اینکه این مجوزها غیرقابل تغییر باشند، توصیه میشود آنها را با گذرواژه از طریق صفحه افزودن گذرواژه تنظیم کنید
|
||||||
@ -1087,7 +1099,7 @@ permissions.selectText.10=جلوگیری از چاپ فرمتهای مختل
|
|||||||
permissions.submit=تغییر
|
permissions.submit=تغییر
|
||||||
|
|
||||||
|
|
||||||
# Remove Password
|
#remove password
|
||||||
removePassword.title=حذف گذرواژه
|
removePassword.title=حذف گذرواژه
|
||||||
removePassword.header=حذف گذرواژه (رمزگشایی)
|
removePassword.header=حذف گذرواژه (رمزگشایی)
|
||||||
removePassword.selectText.1=PDFی را برای رمزگشایی انتخاب کنید
|
removePassword.selectText.1=PDFی را برای رمزگشایی انتخاب کنید
|
||||||
@ -1095,7 +1107,7 @@ removePassword.selectText.2=گذرواژه
|
|||||||
removePassword.submit=حذف
|
removePassword.submit=حذف
|
||||||
|
|
||||||
|
|
||||||
# Change Metadata
|
#changeMetadata
|
||||||
changeMetadata.title=عنوان:
|
changeMetadata.title=عنوان:
|
||||||
changeMetadata.header=تغییر متادادهها
|
changeMetadata.header=تغییر متادادهها
|
||||||
changeMetadata.selectText.1=لطفاً متغیرهایی که مایل به تغییر آنها هستید را ویرایش کنید
|
changeMetadata.selectText.1=لطفاً متغیرهایی که مایل به تغییر آنها هستید را ویرایش کنید
|
||||||
@ -1114,7 +1126,7 @@ changeMetadata.selectText.5=افزودن ورودی متاداده سفارشی
|
|||||||
changeMetadata.submit=تغییر
|
changeMetadata.submit=تغییر
|
||||||
|
|
||||||
|
|
||||||
# PDF to PDF/A
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF به PDF/A
|
pdfToPDFA.title=PDF به PDF/A
|
||||||
pdfToPDFA.header=PDF به PDF/A
|
pdfToPDFA.header=PDF به PDF/A
|
||||||
pdfToPDFA.credit=این سرویس از qpdf برای تبدیل PDF/A استفاده میکند
|
pdfToPDFA.credit=این سرویس از qpdf برای تبدیل PDF/A استفاده میکند
|
||||||
@ -1124,7 +1136,7 @@ pdfToPDFA.outputFormat=فرمت خروجی
|
|||||||
pdfToPDFA.pdfWithDigitalSignature=PDF حاوی یک امضای دیجیتال است. این در مرحله بعد حذف خواهد شد.
|
pdfToPDFA.pdfWithDigitalSignature=PDF حاوی یک امضای دیجیتال است. این در مرحله بعد حذف خواهد شد.
|
||||||
|
|
||||||
|
|
||||||
# PDF to Word
|
#PDFToWord
|
||||||
PDFToWord.title=PDF به ورد
|
PDFToWord.title=PDF به ورد
|
||||||
PDFToWord.header=PDF به ورد
|
PDFToWord.header=PDF به ورد
|
||||||
PDFToWord.selectText.1=فرمت فایل خروجی
|
PDFToWord.selectText.1=فرمت فایل خروجی
|
||||||
@ -1132,7 +1144,7 @@ PDFToWord.credit=این سرویس از LibreOffice برای تبدیل فایل
|
|||||||
PDFToWord.submit=تبدیل
|
PDFToWord.submit=تبدیل
|
||||||
|
|
||||||
|
|
||||||
# PDF to Presentation
|
#PDFToPresentation
|
||||||
PDFToPresentation.title=PDF به ارائه
|
PDFToPresentation.title=PDF به ارائه
|
||||||
PDFToPresentation.header=PDF به ارائه
|
PDFToPresentation.header=PDF به ارائه
|
||||||
PDFToPresentation.selectText.1=فرمت فایل خروجی
|
PDFToPresentation.selectText.1=فرمت فایل خروجی
|
||||||
@ -1140,7 +1152,7 @@ PDFToPresentation.credit=این سرویس از LibreOffice برای تبدیل
|
|||||||
PDFToPresentation.submit=تبدیل
|
PDFToPresentation.submit=تبدیل
|
||||||
|
|
||||||
|
|
||||||
# PDF to Text
|
#PDFToText
|
||||||
PDFToText.title=PDF به RTF (متن)
|
PDFToText.title=PDF به RTF (متن)
|
||||||
PDFToText.header=PDF به RTF (متن)
|
PDFToText.header=PDF به RTF (متن)
|
||||||
PDFToText.selectText.1=فرمت فایل خروجی
|
PDFToText.selectText.1=فرمت فایل خروجی
|
||||||
@ -1148,27 +1160,26 @@ PDFToText.credit=این سرویس از LibreOffice برای تبدیل فایل
|
|||||||
PDFToText.submit=تبدیل
|
PDFToText.submit=تبدیل
|
||||||
|
|
||||||
|
|
||||||
# PDF to HTML
|
#PDFToHTML
|
||||||
PDFToHTML.title=PDF به HTML
|
PDFToHTML.title=PDF به HTML
|
||||||
PDFToHTML.header=PDF به HTML
|
PDFToHTML.header=PDF به HTML
|
||||||
PDFToHTML.credit=این سرویس از pdftohtml برای تبدیل فایل استفاده میکند.
|
PDFToHTML.credit=این سرویس از pdftohtml برای تبدیل فایل استفاده میکند.
|
||||||
PDFToHTML.submit=تبدیل
|
PDFToHTML.submit=تبدیل
|
||||||
|
|
||||||
|
|
||||||
# PDF to XML
|
#PDFToXML
|
||||||
PDFToXML.title=PDF به XML
|
PDFToXML.title=PDF به XML
|
||||||
PDFToXML.header=PDF به XML
|
PDFToXML.header=PDF به XML
|
||||||
PDFToXML.credit=این سرویس از LibreOffice برای تبدیل فایل استفاده میکند.
|
PDFToXML.credit=این سرویس از LibreOffice برای تبدیل فایل استفاده میکند.
|
||||||
PDFToXML.submit=تبدیل
|
PDFToXML.submit=تبدیل
|
||||||
|
|
||||||
# PDF to CSV
|
#PDFToCSV
|
||||||
PDFToCSV.title=PDF به CSV
|
PDFToCSV.title=PDF به CSV
|
||||||
PDFToCSV.header=PDF به CSV
|
PDFToCSV.header=PDF به CSV
|
||||||
PDFToCSV.prompt=صفحهای که میخواهید جدول استخراج شود را انتخاب کنید
|
PDFToCSV.prompt=صفحهای که میخواهید جدول استخراج شود را انتخاب کنید
|
||||||
PDFToCSV.submit=استخراج
|
PDFToCSV.submit=استخراج
|
||||||
|
|
||||||
|
#split-by-size-or-count
|
||||||
# Split by Size or Count
|
|
||||||
split-by-size-or-count.title=تقسیم PDF بر اساس اندازه یا تعداد
|
split-by-size-or-count.title=تقسیم PDF بر اساس اندازه یا تعداد
|
||||||
split-by-size-or-count.header=تقسیم PDF بر اساس اندازه یا تعداد
|
split-by-size-or-count.header=تقسیم PDF بر اساس اندازه یا تعداد
|
||||||
split-by-size-or-count.type.label=انتخاب نوع تقسیم
|
split-by-size-or-count.type.label=انتخاب نوع تقسیم
|
||||||
@ -1180,7 +1191,7 @@ split-by-size-or-count.value.placeholder=اندازه را وارد کنید (م
|
|||||||
split-by-size-or-count.submit=ارسال
|
split-by-size-or-count.submit=ارسال
|
||||||
|
|
||||||
|
|
||||||
# Overlay PDFs
|
#overlay-pdfs
|
||||||
overlay-pdfs.header=ترکیب فایلهای PDF
|
overlay-pdfs.header=ترکیب فایلهای PDF
|
||||||
overlay-pdfs.baseFile.label=انتخاب فایل پایه PDF
|
overlay-pdfs.baseFile.label=انتخاب فایل پایه PDF
|
||||||
overlay-pdfs.overlayFiles.label=انتخاب فایلهای ترکیبی PDF
|
overlay-pdfs.overlayFiles.label=انتخاب فایلهای ترکیبی PDF
|
||||||
@ -1196,7 +1207,7 @@ overlay-pdfs.position.background=پسزمینه
|
|||||||
overlay-pdfs.submit=ارسال
|
overlay-pdfs.submit=ارسال
|
||||||
|
|
||||||
|
|
||||||
# Split by Sections
|
#split-by-sections
|
||||||
split-by-sections.title=تقسیم PDF به بخشها
|
split-by-sections.title=تقسیم PDF به بخشها
|
||||||
split-by-sections.header=تقسیم PDF به بخشها
|
split-by-sections.header=تقسیم PDF به بخشها
|
||||||
split-by-sections.horizontal.label=تقسیمات افقی
|
split-by-sections.horizontal.label=تقسیمات افقی
|
||||||
@ -1207,7 +1218,7 @@ split-by-sections.submit=تقسیم PDF
|
|||||||
split-by-sections.merge=ادغام به یک PDF
|
split-by-sections.merge=ادغام به یک PDF
|
||||||
|
|
||||||
|
|
||||||
# Print File
|
#printFile
|
||||||
printFile.title=چاپ فایل
|
printFile.title=چاپ فایل
|
||||||
printFile.header=چاپ فایل به چاپگر
|
printFile.header=چاپ فایل به چاپگر
|
||||||
printFile.selectText.1=انتخاب فایل برای چاپ
|
printFile.selectText.1=انتخاب فایل برای چاپ
|
||||||
@ -1215,7 +1226,7 @@ printFile.selectText.2=نام چاپگر را وارد کنید
|
|||||||
printFile.submit=چاپ
|
printFile.submit=چاپ
|
||||||
|
|
||||||
|
|
||||||
# Licenses
|
#licenses
|
||||||
licenses.nav=مجوزها
|
licenses.nav=مجوزها
|
||||||
licenses.title=مجوزهای شخص ثالث
|
licenses.title=مجوزهای شخص ثالث
|
||||||
licenses.header=مجوزهای شخص ثالث
|
licenses.header=مجوزهای شخص ثالث
|
||||||
@ -1223,7 +1234,7 @@ licenses.module=ماژول
|
|||||||
licenses.version=نسخه
|
licenses.version=نسخه
|
||||||
licenses.license=مجوز
|
licenses.license=مجوز
|
||||||
|
|
||||||
# Survey
|
#survey
|
||||||
survey.nav=نظرسنجی
|
survey.nav=نظرسنجی
|
||||||
survey.title=نظرسنجی Stirling-PDF
|
survey.title=نظرسنجی Stirling-PDF
|
||||||
survey.description=Stirling-PDF هیچ ردیابی ندارد، بنابراین ما میخواهیم از کاربران خود بشنویم تا Stirling-PDF را بهبود دهیم!
|
survey.description=Stirling-PDF هیچ ردیابی ندارد، بنابراین ما میخواهیم از کاربران خود بشنویم تا Stirling-PDF را بهبود دهیم!
|
||||||
@ -1235,7 +1246,7 @@ survey.button=شرکت در نظرسنجی
|
|||||||
survey.dontShowAgain=دیگر نشان نده
|
survey.dontShowAgain=دیگر نشان نده
|
||||||
|
|
||||||
|
|
||||||
# Error
|
#error
|
||||||
error.sorry=متأسفیم برای مشکل موجود!
|
error.sorry=متأسفیم برای مشکل موجود!
|
||||||
error.needHelp=نیاز به کمک / یافتن مشکلی؟
|
error.needHelp=نیاز به کمک / یافتن مشکلی؟
|
||||||
error.contactTip=اگر هنوز مشکلی دارید، دریغ نکنید که با ما تماس بگیرید. میتوانید یک تیکت در صفحه GitHub ما ارسال کنید یا از طریق Discord با ما تماس بگیرید:
|
error.contactTip=اگر هنوز مشکلی دارید، دریغ نکنید که با ما تماس بگیرید. میتوانید یک تیکت در صفحه GitHub ما ارسال کنید یا از طریق Discord با ما تماس بگیرید:
|
||||||
@ -1249,14 +1260,13 @@ error.githubSubmit=GitHub - ارسال تیکت
|
|||||||
error.discordSubmit=Discord - ارسال پست پشتیبانی
|
error.discordSubmit=Discord - ارسال پست پشتیبانی
|
||||||
|
|
||||||
|
|
||||||
# Remove Image
|
#remove-image
|
||||||
removeImage.title=حذف تصویر
|
removeImage.title=حذف تصویر
|
||||||
removeImage.header=حذف تصویر
|
removeImage.header=حذف تصویر
|
||||||
removeImage.removeImage=حذف تصویر
|
removeImage.removeImage=حذف تصویر
|
||||||
removeImage.submit=حذف تصویر
|
removeImage.submit=حذف تصویر
|
||||||
|
|
||||||
|
|
||||||
# Split by Chapters
|
|
||||||
splitByChapters.title=تقسیم PDF بر اساس فصلها
|
splitByChapters.title=تقسیم PDF بر اساس فصلها
|
||||||
splitByChapters.header=تقسیم PDF بر اساس فصلها
|
splitByChapters.header=تقسیم PDF بر اساس فصلها
|
||||||
splitByChapters.bookmarkLevel=سطح نشانک
|
splitByChapters.bookmarkLevel=سطح نشانک
|
||||||
@ -1268,20 +1278,20 @@ splitByChapters.desc.3=شامل متادیتا: اگر انتخاب شده، م
|
|||||||
splitByChapters.desc.4=اجازهی تکرار: اگر انتخاب شده باشد، اجازه میدهد نشانکهای متعدد در یک صفحه، فایلهای PDF جداگانه ایجاد کنند.
|
splitByChapters.desc.4=اجازهی تکرار: اگر انتخاب شده باشد، اجازه میدهد نشانکهای متعدد در یک صفحه، فایلهای PDF جداگانه ایجاد کنند.
|
||||||
splitByChapters.submit=تقسیم PDF
|
splitByChapters.submit=تقسیم PDF
|
||||||
|
|
||||||
# File Chooser
|
#File Chooser
|
||||||
fileChooser.click=کلیک کنید
|
fileChooser.click=کلیک کنید
|
||||||
fileChooser.or=یا
|
fileChooser.or=یا
|
||||||
fileChooser.dragAndDrop=بکشید و رها کنید
|
fileChooser.dragAndDrop=بکشید و رها کنید
|
||||||
fileChooser.hoveredDragAndDrop=فایل(های) خود را اینجا بکشید و رها کنید
|
fileChooser.hoveredDragAndDrop=فایل(های) خود را اینجا بکشید و رها کنید
|
||||||
|
|
||||||
# Release Notes
|
#release notes
|
||||||
releases.footer=نسخهها
|
releases.footer=نسخهها
|
||||||
releases.title=یادداشتهای نسخه
|
releases.title=یادداشتهای نسخه
|
||||||
releases.header=یادداشتهای نسخه
|
releases.header=یادداشتهای نسخه
|
||||||
releases.current.version=نسخه فعلی
|
releases.current.version=نسخه فعلی
|
||||||
releases.note=یادداشتهای نسخه فقط به زبان انگلیسی موجود است
|
releases.note=یادداشتهای نسخه فقط به زبان انگلیسی موجود است
|
||||||
|
|
||||||
# Validate Signature
|
#Validate Signature
|
||||||
validateSignature.title=اعتبارسنجی امضاهای PDF
|
validateSignature.title=اعتبارسنجی امضاهای PDF
|
||||||
validateSignature.header=اعتبارسنجی امضای دیجیتال
|
validateSignature.header=اعتبارسنجی امضای دیجیتال
|
||||||
validateSignature.selectPDF=فایل PDF امضاشده را انتخاب کنید
|
validateSignature.selectPDF=فایل PDF امضاشده را انتخاب کنید
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) sélectionnées
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=Cette fonctionnalité est aussi disponible dans la <a href="{0}">page de l'outil multifonction</a>. Allez-y pour une interface page par page améliorée et des fonctionnalités additionnelles !
|
multiTool-advert.message=Cette fonctionnalité est aussi disponible dans la <a href="{0}">page de l'outil multifonction</a>. Allez-y pour une interface page par page améliorée et des fonctionnalités additionnelles !
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Pagina(e) selezionata(e)
|
|||||||
multiTool.undo=Annulla
|
multiTool.undo=Annulla
|
||||||
multiTool.redo=Rifai
|
multiTool.redo=Rifai
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=Questo file è protetto da password. Inserisci la password:
|
||||||
|
decrypt.cancelled=Operazione annullata per il PDF: {0}
|
||||||
|
decrypt.noPassword=Nessuna password fornita per il PDF crittografato: {0}
|
||||||
|
decrypt.invalidPassword=Riprova con la password corretta.
|
||||||
|
decrypt.invalidPasswordHeader=Password errata o crittografia non supportata per il PDF: {0}
|
||||||
|
decrypt.unexpectedError=Si è verificato un errore durante l'elaborazione del file. Riprova..
|
||||||
|
decrypt.serverError=Errore del server durante la decrittazione: {0}
|
||||||
|
decrypt.success=File decrittografato con successo.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=Questa funzione è disponibile anche nella nostra <a href="{0}">pagina multi-strumento</a>. Scoprila per un'interfaccia utente pagina per pagina migliorata e funzionalità aggiuntive!
|
multiTool-advert.message=Questa funzione è disponibile anche nella nostra <a href="{0}">pagina multi-strumento</a>. Scoprila per un'interfaccia utente pagina per pagina migliorata e funzionalità aggiuntive!
|
||||||
|
|
||||||
|
@ -56,12 +56,12 @@ userNotFoundMessage=ユーザーが見つかりません。
|
|||||||
incorrectPasswordMessage=現在のパスワードが正しくありません。
|
incorrectPasswordMessage=現在のパスワードが正しくありません。
|
||||||
usernameExistsMessage=新しいユーザー名はすでに存在します。
|
usernameExistsMessage=新しいユーザー名はすでに存在します。
|
||||||
invalidUsernameMessage=ユーザー名が無効です。ユーザー名には文字、数字、およびそれに続く特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
|
invalidUsernameMessage=ユーザー名が無効です。ユーザー名には文字、数字、およびそれに続く特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
|
||||||
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
|
invalidPasswordMessage=パスワードは空にすることはできません。また、先頭・末尾にスペースを含めることもできません。
|
||||||
confirmPasswordErrorMessage=新しいパスワードと新しいパスワードの確認は一致する必要があります。
|
confirmPasswordErrorMessage=新しいパスワードと新しいパスワードの確認は一致する必要があります。
|
||||||
deleteCurrentUserMessage=現在ログインしているユーザーは削除できません。
|
deleteCurrentUserMessage=現在ログインしているユーザーは削除できません。
|
||||||
deleteUsernameExistsMessage=そのユーザー名は存在しないため削除できません。
|
deleteUsernameExistsMessage=そのユーザー名は存在しないため削除できません。
|
||||||
downgradeCurrentUserMessage=現在のユーザーの役割をダウングレードできません
|
downgradeCurrentUserMessage=現在のユーザーの役割をダウングレードできません
|
||||||
disabledCurrentUserMessage=The current user cannot be disabled
|
disabledCurrentUserMessage=現在のユーザーを無効にすることはできません
|
||||||
downgradeCurrentUserLongMessage=現在のユーザーの役割をダウングレードできません。したがって、現在のユーザーは表示されません。
|
downgradeCurrentUserLongMessage=現在のユーザーの役割をダウングレードできません。したがって、現在のユーザーは表示されません。
|
||||||
userAlreadyExistsOAuthMessage=ユーザーは既にOAuth2ユーザーとして存在します。
|
userAlreadyExistsOAuthMessage=ユーザーは既にOAuth2ユーザーとして存在します。
|
||||||
userAlreadyExistsWebMessage=ユーザーは既にWebユーザーとして存在します。
|
userAlreadyExistsWebMessage=ユーザーは既にWebユーザーとして存在します。
|
||||||
@ -76,12 +76,12 @@ donate=寄付する
|
|||||||
color=色
|
color=色
|
||||||
sponsor=スポンサー
|
sponsor=スポンサー
|
||||||
info=Info
|
info=Info
|
||||||
pro=Pro
|
pro=pro
|
||||||
page=Page
|
page=ページ
|
||||||
pages=Pages
|
pages=ページ
|
||||||
loading=Loading...
|
loading=読込中...
|
||||||
addToDoc=Add to Document
|
addToDoc=ドキュメントに追加
|
||||||
reset=Reset
|
reset=リセット
|
||||||
|
|
||||||
legal.privacy=プライバシーポリシー
|
legal.privacy=プライバシーポリシー
|
||||||
legal.terms=利用規約
|
legal.terms=利用規約
|
||||||
@ -92,7 +92,7 @@ legal.impressum=著作権利者情報
|
|||||||
###############
|
###############
|
||||||
# Pipeline #
|
# Pipeline #
|
||||||
###############
|
###############
|
||||||
pipeline.header=パイプラインメニュー (Alpha)
|
pipeline.header=パイプラインメニュー (Beta)
|
||||||
pipeline.uploadButton=カスタムのアップロード
|
pipeline.uploadButton=カスタムのアップロード
|
||||||
pipeline.configureButton=設定
|
pipeline.configureButton=設定
|
||||||
pipeline.defaultOption=カスタム
|
pipeline.defaultOption=カスタム
|
||||||
@ -117,21 +117,21 @@ pipelineOptions.validateButton=検証
|
|||||||
########################
|
########################
|
||||||
# ENTERPRISE EDITION #
|
# ENTERPRISE EDITION #
|
||||||
########################
|
########################
|
||||||
enterpriseEdition.button=Upgrade to Pro
|
enterpriseEdition.button=Proにアップグレード
|
||||||
enterpriseEdition.warning=This feature is only available to Pro users.
|
enterpriseEdition.warning=この機能はProユーザーのみが利用できます。
|
||||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
enterpriseEdition.yamlAdvert=Stirling PDF Proは、YAML構成ファイルやその他のSSO機能をサポートしています。
|
||||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
enterpriseEdition.ssoAdvert=より多くのユーザー管理機能をお探しですか? Stirling PDF Proをご覧ください
|
||||||
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Analytics #
|
# Analytics #
|
||||||
#################
|
#################
|
||||||
analytics.title=Do you want make Stirling PDF better?
|
analytics.title=Stirling PDFをもっと良くしたいですか?
|
||||||
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
analytics.paragraph1=Stirling PDFでは、製品の改善に役立つ分析機能をオプトインしています。個人情報やファイルの内容を追跡することはありません。
|
||||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
analytics.paragraph2=Stirling-PDFの成長を支援しユーザーをより深く理解できるように分析を有効にすることを検討してください。
|
||||||
analytics.enable=Enable analytics
|
analytics.enable=分析を有効にする
|
||||||
analytics.disable=Disable analytics
|
analytics.disable=分析を無効にする
|
||||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
analytics.settings=config/settings.ymlファイルでアナリティクスの設定を変更できます。
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@ -142,14 +142,14 @@ navbar.language=言語
|
|||||||
navbar.settings=設定
|
navbar.settings=設定
|
||||||
navbar.allTools=ツール
|
navbar.allTools=ツール
|
||||||
navbar.multiTool=マルチツール
|
navbar.multiTool=マルチツール
|
||||||
navbar.search=Search
|
navbar.search=検索
|
||||||
navbar.sections.organize=整理
|
navbar.sections.organize=整理
|
||||||
navbar.sections.convertTo=PDFへ変換
|
navbar.sections.convertTo=PDFへ変換
|
||||||
navbar.sections.convertFrom=PDFから変換
|
navbar.sections.convertFrom=PDFから変換
|
||||||
navbar.sections.security=署名とセキュリティ
|
navbar.sections.security=署名とセキュリティ
|
||||||
navbar.sections.advance=アドバンスド
|
navbar.sections.advance=アドバンスド
|
||||||
navbar.sections.edit=閲覧と編集
|
navbar.sections.edit=閲覧と編集
|
||||||
navbar.sections.popular=Popular
|
navbar.sections.popular=人気
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
@ -208,7 +208,7 @@ adminUserSettings.user=ユーザー
|
|||||||
adminUserSettings.addUser=新しいユーザを追加
|
adminUserSettings.addUser=新しいユーザを追加
|
||||||
adminUserSettings.deleteUser=ユーザの削除
|
adminUserSettings.deleteUser=ユーザの削除
|
||||||
adminUserSettings.confirmDeleteUser=ユーザを本当に削除しますか?
|
adminUserSettings.confirmDeleteUser=ユーザを本当に削除しますか?
|
||||||
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
|
adminUserSettings.confirmChangeUserStatus=ユーザーを無効/有効にする必要がありますか?
|
||||||
adminUserSettings.usernameInfo=ユーザー名には、文字、数字、および次の特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
|
adminUserSettings.usernameInfo=ユーザー名には、文字、数字、および次の特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
|
||||||
adminUserSettings.roles=役割
|
adminUserSettings.roles=役割
|
||||||
adminUserSettings.role=役割
|
adminUserSettings.role=役割
|
||||||
@ -247,8 +247,8 @@ database.fileNotFound=ファイルが見つかりません
|
|||||||
database.fileNullOrEmpty=ファイルは null または空であってはなりません
|
database.fileNullOrEmpty=ファイルは null または空であってはなりません
|
||||||
database.failedImportFile=ファイルのインポートに失敗
|
database.failedImportFile=ファイルのインポートに失敗
|
||||||
|
|
||||||
session.expired=Your session has expired. Please refresh the page and try again.
|
session.expired=セッションが期限切れです。ページを更新してもう一度お試しください。
|
||||||
session.refreshPage=Refresh Page
|
session.refreshPage=ページを更新
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@ -488,52 +488,52 @@ overlay-pdfs.tags=Overlay
|
|||||||
|
|
||||||
home.split-by-sections.title=PDFをセクションで分割
|
home.split-by-sections.title=PDFをセクションで分割
|
||||||
home.split-by-sections.desc=PDFの各ページを縦横に分割します。
|
home.split-by-sections.desc=PDFの各ページを縦横に分割します。
|
||||||
split-by-sections.tags=Section Split, Divide, Customize
|
split-by-sections.tags=Section Split, Divide, Customize,Customise
|
||||||
|
|
||||||
home.AddStampRequest.title=PDFにスタンプを追加
|
home.AddStampRequest.title=PDFにスタンプを追加
|
||||||
home.AddStampRequest.desc=設定した位置にテキストや画像のスタンプを追加できます
|
home.AddStampRequest.desc=設定した位置にテキストや画像のスタンプを追加できます
|
||||||
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize
|
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize,Customise
|
||||||
|
|
||||||
|
|
||||||
home.PDFToBook.title=PDFを書籍に変換
|
home.PDFToBook.title=PDFを書籍に変換
|
||||||
home.PDFToBook.desc=calibreを使用してPDFを書籍/コミック形式に変換します
|
home.PDFToBook.desc=calibreを使用してPDFを書籍/コミック形式に変換します
|
||||||
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
|
||||||
|
|
||||||
home.BookToPDF.title=PDFを書籍に変換
|
home.BookToPDF.title=PDFを書籍に変換
|
||||||
home.BookToPDF.desc=calibreを使用してPDFを書籍/コミック形式に変換します
|
home.BookToPDF.desc=calibreを使用してPDFを書籍/コミック形式に変換します
|
||||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
|
||||||
|
|
||||||
home.removeImagePdf.title=画像の削除
|
home.removeImagePdf.title=画像の削除
|
||||||
home.removeImagePdf.desc=PDFから画像を削除してファイルサイズを小さくします
|
home.removeImagePdf.desc=PDFから画像を削除してファイルサイズを小さくします
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
|
||||||
home.splitPdfByChapters.title=Split PDF by Chapters
|
home.splitPdfByChapters.title=PDFをチャプターごとに分割
|
||||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
home.splitPdfByChapters.desc=チャプターの構造に基づいてPDFを複数のファイルに分割します
|
||||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
home.validateSignature.title=Validate PDF Signature
|
home.validateSignature.title=PDF署名の検証
|
||||||
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
|
home.validateSignature.desc=PDF文書のデジタル署名と証明書を検証します
|
||||||
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=色の置換・反転
|
||||||
replace-color.header=Replace-Invert Color PDF
|
replace-color.header=PDFの色の置換・反転
|
||||||
home.replaceColorPdf.title=Replace and Invert Color
|
home.replaceColorPdf.title=色の置換と反転
|
||||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
home.replaceColorPdf.desc=PDF内のテキストと背景の色を置き換え、PDFのフルカラーを反転してファイルサイズを縮小します。
|
||||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
replace-color.selectText.1=Replace or Invert color Options
|
replace-color.selectText.1=色の置換または反転オプション
|
||||||
replace-color.selectText.2=Default(Default high contrast colors)
|
replace-color.selectText.2=デフォルト(デフォルトの高コントラスト色)
|
||||||
replace-color.selectText.3=Custom(Customized colors)
|
replace-color.selectText.3=カスタム(カスタマイズされた色)
|
||||||
replace-color.selectText.4=Full-Invert(Invert all colors)
|
replace-color.selectText.4=フル反転(すべての色を反転)
|
||||||
replace-color.selectText.5=High contrast color options
|
replace-color.selectText.5=高コントラストカラーオプション
|
||||||
replace-color.selectText.6=white text on black background
|
replace-color.selectText.6=黒背景に白文字
|
||||||
replace-color.selectText.7=Black text on white background
|
replace-color.selectText.7=白背景に黒文字
|
||||||
replace-color.selectText.8=Yellow text on black background
|
replace-color.selectText.8=黒背景に黄色文字
|
||||||
replace-color.selectText.9=Green text on black background
|
replace-color.selectText.9=黒背景に緑文字
|
||||||
replace-color.selectText.10=Choose text Color
|
replace-color.selectText.10=テキストの色を選択
|
||||||
replace-color.selectText.11=Choose background Color
|
replace-color.selectText.11=背景色を選択
|
||||||
replace-color.submit=Replace
|
replace-color.submit=置換
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -560,9 +560,9 @@ login.oauth2AccessDenied=アクセス拒否
|
|||||||
login.oauth2InvalidTokenResponse=無効なトークン応答
|
login.oauth2InvalidTokenResponse=無効なトークン応答
|
||||||
login.oauth2InvalidIdToken=無効なIDトークン
|
login.oauth2InvalidIdToken=無効なIDトークン
|
||||||
login.userIsDisabled=ユーザーは非アクティブ化されており、現在このユーザー名でのログインはブロックされています。管理者に連絡してください。
|
login.userIsDisabled=ユーザーは非アクティブ化されており、現在このユーザー名でのログインはブロックされています。管理者に連絡してください。
|
||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=すでにログインしています
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=デバイスからログアウトしてもう一度お試しください。
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=アクティブなセッションが多すぎます
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=自動塗りつぶし
|
autoRedact.title=自動塗りつぶし
|
||||||
@ -578,8 +578,8 @@ autoRedact.submitButton=送信
|
|||||||
|
|
||||||
|
|
||||||
#showJS
|
#showJS
|
||||||
showJS.title=JavaScriptを表示
|
showJS.title=Javascriptを表示
|
||||||
showJS.header=JavaScriptを表示
|
showJS.header=Javascriptを表示
|
||||||
showJS.downloadJS=Javascriptをダウンロード
|
showJS.downloadJS=Javascriptをダウンロード
|
||||||
showJS.submit=表示
|
showJS.submit=表示
|
||||||
|
|
||||||
@ -757,7 +757,7 @@ certSign.showSig=署名を表示
|
|||||||
certSign.reason=理由
|
certSign.reason=理由
|
||||||
certSign.location=場所
|
certSign.location=場所
|
||||||
certSign.name=名前
|
certSign.name=名前
|
||||||
certSign.showLogo=Show Logo
|
certSign.showLogo=ロゴを表示
|
||||||
certSign.submit=PDFに署名
|
certSign.submit=PDFに署名
|
||||||
|
|
||||||
|
|
||||||
@ -792,9 +792,9 @@ compare.highlightColor.2=ハイライトカラー 2:
|
|||||||
compare.document.1=ドキュメント 1
|
compare.document.1=ドキュメント 1
|
||||||
compare.document.2=ドキュメント 2
|
compare.document.2=ドキュメント 2
|
||||||
compare.submit=比較
|
compare.submit=比較
|
||||||
compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced
|
compare.complex.message=提供された文書の一方または両方が大きなファイルであるため、比較の精度が低下する可能性があります。
|
||||||
compare.large.file.message=One or Both of the provided documents are too large to process
|
compare.large.file.message=提供された文書の1つまたは両方が大きすぎて処理できません
|
||||||
compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison.
|
compare.no.text.message=選択したPDFの1つまたは両方にテキストコンテンツがありません。比較するには、テキストを含むPDFを選択してください。
|
||||||
|
|
||||||
#BookToPDF
|
#BookToPDF
|
||||||
BookToPDF.title=書籍やコミックをPDFに変換
|
BookToPDF.title=書籍やコミックをPDFに変換
|
||||||
@ -803,8 +803,8 @@ BookToPDF.credit=calibreを使用
|
|||||||
BookToPDF.submit=変換
|
BookToPDF.submit=変換
|
||||||
|
|
||||||
#PDFToBook
|
#PDFToBook
|
||||||
PDFToBook.title=書籍をPDFに変換
|
PDFToBook.title=PDFを書籍に変換
|
||||||
PDFToBook.header=書籍をPDFに変換
|
PDFToBook.header=PDFを書籍に変換
|
||||||
PDFToBook.selectText.1=フォーマット
|
PDFToBook.selectText.1=フォーマット
|
||||||
PDFToBook.credit=calibreを使用
|
PDFToBook.credit=calibreを使用
|
||||||
PDFToBook.submit=変換
|
PDFToBook.submit=変換
|
||||||
@ -817,17 +817,17 @@ sign.draw=署名を書く
|
|||||||
sign.text=テキスト入力
|
sign.text=テキスト入力
|
||||||
sign.clear=クリア
|
sign.clear=クリア
|
||||||
sign.add=追加
|
sign.add=追加
|
||||||
sign.saved=Saved Signatures
|
sign.saved=保存された署名
|
||||||
sign.save=Save Signature
|
sign.save=署名を保存
|
||||||
sign.personalSigs=Personal Signatures
|
sign.personalSigs=個人署名
|
||||||
sign.sharedSigs=Shared Signatures
|
sign.sharedSigs=共有署名
|
||||||
sign.noSavedSigs=No saved signatures found
|
sign.noSavedSigs=保存された署名が見つかりません
|
||||||
sign.addToAll=Add to all pages
|
sign.addToAll=すべてのページに追加
|
||||||
sign.delete=Delete
|
sign.delete=削除
|
||||||
sign.first=First page
|
sign.first=最初のページ
|
||||||
sign.last=Last page
|
sign.last=最後のページ
|
||||||
sign.next=Next page
|
sign.next=次のページ
|
||||||
sign.previous=Previous page
|
sign.previous=前のページ
|
||||||
|
|
||||||
#repair
|
#repair
|
||||||
repair.title=修復
|
repair.title=修復
|
||||||
@ -944,29 +944,39 @@ pdfOrganiser.placeholder=(例:1,3,2または4-8,2,10-12または2n-1)
|
|||||||
multiTool.title=PDFマルチツール
|
multiTool.title=PDFマルチツール
|
||||||
multiTool.header=PDFマルチツール
|
multiTool.header=PDFマルチツール
|
||||||
multiTool.uploadPrompts=ファイル名
|
multiTool.uploadPrompts=ファイル名
|
||||||
multiTool.selectAll=Select All
|
multiTool.selectAll=すべて選択
|
||||||
multiTool.deselectAll=Deselect All
|
multiTool.deselectAll=選択を解除
|
||||||
multiTool.selectPages=Page Select
|
multiTool.selectPages=ページ選択
|
||||||
multiTool.selectedPages=Selected Pages
|
multiTool.selectedPages=選択したページ
|
||||||
multiTool.page=Page
|
multiTool.page=ページ
|
||||||
multiTool.deleteSelected=Delete Selected
|
multiTool.deleteSelected=選択項目を削除
|
||||||
multiTool.downloadAll=Export
|
multiTool.downloadAll=エクスポート
|
||||||
multiTool.downloadSelected=Export Selected
|
multiTool.downloadSelected=選択項目をエクスポート
|
||||||
|
|
||||||
multiTool.insertPageBreak=Insert Page Break
|
multiTool.insertPageBreak=改ページを挿入
|
||||||
multiTool.addFile=Add File
|
multiTool.addFile=ファイルを追加
|
||||||
multiTool.rotateLeft=Rotate Left
|
multiTool.rotateLeft=左回転
|
||||||
multiTool.rotateRight=Rotate Right
|
multiTool.rotateRight=右回転
|
||||||
multiTool.split=Split
|
multiTool.split=分割
|
||||||
multiTool.moveLeft=Move Left
|
multiTool.moveLeft=左に移動
|
||||||
multiTool.moveRight=Move Right
|
multiTool.moveRight=右に移動
|
||||||
multiTool.delete=Delete
|
multiTool.delete=削除
|
||||||
multiTool.dragDropMessage=Page(s) Selected
|
multiTool.dragDropMessage=選択されたページ
|
||||||
multiTool.undo=Undo
|
multiTool.undo=元に戻す
|
||||||
multiTool.redo=Redo
|
multiTool.redo=やり直す
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=このファイルはパスワードで保護されています。パスワードを入力してください:
|
||||||
|
decrypt.cancelled=PDFの操作がキャンセルされました: {0}
|
||||||
|
decrypt.noPassword=暗号化されたPDFにパスワードが指定されていません: {0}
|
||||||
|
decrypt.invalidPassword=正しいパスワードでもう一度お試しください。
|
||||||
|
decrypt.invalidPasswordHeader=PDFのパスワードが正しくないか、暗号化がサポートされていません: {0}
|
||||||
|
decrypt.unexpectedError=ファイルの処理中にエラーが発生しました。もう一度お試しください。
|
||||||
|
decrypt.serverError=復号化中にサーバーエラーが発生しました: {0}
|
||||||
|
decrypt.success=ファイルの暗号化が正常に完了しました。
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=この機能は、<a href="{0}">マルチツール</a>でもご利用いただけます。強化されたページごとのUIと追加機能についてはこちらをご覧ください。
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=PDFを表示
|
viewPdf.title=PDFを表示
|
||||||
@ -1122,8 +1132,8 @@ pdfToPDFA.header=PDFをPDF/Aに変換
|
|||||||
pdfToPDFA.credit=本サービスはPDF/Aの変換にqpdfを使用しています。
|
pdfToPDFA.credit=本サービスはPDF/Aの変換にqpdfを使用しています。
|
||||||
pdfToPDFA.submit=変換
|
pdfToPDFA.submit=変換
|
||||||
pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません
|
pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=出力形式
|
||||||
pdfToPDFA.pdfWithDigitalSignature=PDF にはデジタル署名が含まれています。これは次の手順で削除されます。
|
pdfToPDFA.pdfWithDigitalSignature=PDFにはデジタル署名が含まれています。これは次の手順で削除されます。
|
||||||
|
|
||||||
|
|
||||||
#PDFToWord
|
#PDFToWord
|
||||||
@ -1228,8 +1238,8 @@ licenses.license=ライセンス
|
|||||||
survey.nav=アンケート
|
survey.nav=アンケート
|
||||||
survey.title=Stirling-PDFのアンケート
|
survey.title=Stirling-PDFのアンケート
|
||||||
survey.description=Stirling-PDFには追跡機能がないため、Stirling-PDFをより良くするために皆様の意見を聞かせてください!
|
survey.description=Stirling-PDFには追跡機能がないため、Stirling-PDFをより良くするために皆様の意見を聞かせてください!
|
||||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
survey.changes=Stirling-PDFは前回の調査から変更されました。詳細についてはこちらのブログ投稿をご覧ください。
|
||||||
survey.changes2=With these changes we are getting paid business support and funding
|
survey.changes2=これらの変更により私たちは有償のビジネスサポートと資金援助を受けています
|
||||||
survey.please=アンケートにご協力ください!
|
survey.please=アンケートにご協力ください!
|
||||||
survey.disabled=(アンケートのポップアップは、次の更新では無効になりますが、ページの下部に表示されます。)
|
survey.disabled=(アンケートのポップアップは、次の更新では無効になりますが、ページの下部に表示されます。)
|
||||||
survey.button=アンケートに答える
|
survey.button=アンケートに答える
|
||||||
@ -1257,61 +1267,61 @@ removeImage.removeImage=画像の削除
|
|||||||
removeImage.submit=画像を削除
|
removeImage.submit=画像を削除
|
||||||
|
|
||||||
|
|
||||||
splitByChapters.title=Split PDF by Chapters
|
splitByChapters.title=PDFをチャプターごとに分割
|
||||||
splitByChapters.header=Split PDF by Chapters
|
splitByChapters.header=PDFをチャプターごとに分割
|
||||||
splitByChapters.bookmarkLevel=Bookmark Level
|
splitByChapters.bookmarkLevel=ブックマークレベル
|
||||||
splitByChapters.includeMetadata=Include Metadata
|
splitByChapters.includeMetadata=メタデータを含める
|
||||||
splitByChapters.allowDuplicates=Allow Duplicates
|
splitByChapters.allowDuplicates=重複を許可する
|
||||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
splitByChapters.desc.1=このツールは、チャプター構造に基づいてPDFファイルを複数のPDFに分割します。
|
||||||
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
splitByChapters.desc.2=ブックマークレベル:分割に使用するブックマークのレベルを選択します(最上位レベルの場合は0、第2レベルの場合は1など)。
|
||||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
splitByChapters.desc.3=メタデータを含める:チェックすると、元のPDFのメタデータが各分割PDFに含まれます。
|
||||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
splitByChapters.desc.4=重複を許可:チェックすると同じページ上の複数のブックマークから個別のPDFを作成できます。
|
||||||
splitByChapters.submit=Split PDF
|
splitByChapters.submit=PDFを分割
|
||||||
|
|
||||||
#File Chooser
|
#File Chooser
|
||||||
fileChooser.click=Click
|
fileChooser.click=クリック
|
||||||
fileChooser.or=or
|
fileChooser.or=または
|
||||||
fileChooser.dragAndDrop=Drag & Drop
|
fileChooser.dragAndDrop=ドラッグ&ドロップ
|
||||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
fileChooser.hoveredDragAndDrop=ファイルをここにドラッグ&ドロップ
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Releases
|
releases.footer=リリース
|
||||||
releases.title=Release Notes
|
releases.title=リリースノート
|
||||||
releases.header=Release Notes
|
releases.header=リリースノート
|
||||||
releases.current.version=Current Release
|
releases.current.version=現在のリリース
|
||||||
releases.note=Release notes are only available in English
|
releases.note=リリースノートは英語でのみで提供されています
|
||||||
|
|
||||||
#Validate Signature
|
#Validate Signature
|
||||||
validateSignature.title=Validate PDF Signatures
|
validateSignature.title=PDF署名の検証
|
||||||
validateSignature.header=Validate Digital Signatures
|
validateSignature.header=デジタル署名の検証
|
||||||
validateSignature.selectPDF=Select signed PDF file
|
validateSignature.selectPDF=署名済みPDFファイルを選択
|
||||||
validateSignature.submit=Validate Signatures
|
validateSignature.submit=署名の検証
|
||||||
validateSignature.results=Validation Results
|
validateSignature.results=検証結果
|
||||||
validateSignature.status=Status
|
validateSignature.status=状態
|
||||||
validateSignature.signer=Signer
|
validateSignature.signer=署名者
|
||||||
validateSignature.date=Date
|
validateSignature.date=日付
|
||||||
validateSignature.reason=Reason
|
validateSignature.reason=理由
|
||||||
validateSignature.location=Location
|
validateSignature.location=場所
|
||||||
validateSignature.noSignatures=No digital signatures found in this document
|
validateSignature.noSignatures=この文書にはデジタル署名が見つかりません
|
||||||
validateSignature.status.valid=Valid
|
validateSignature.status.valid=有効
|
||||||
validateSignature.status.invalid=Invalid
|
validateSignature.status.invalid=無効
|
||||||
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
|
validateSignature.chain.invalid=証明書チェーンの検証に失敗しました - 署名者の身元を確認できません
|
||||||
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
|
validateSignature.trust.invalid=証明書が信頼ストアにありません - ソースを検証できません
|
||||||
validateSignature.cert.expired=Certificate has expired
|
validateSignature.cert.expired=証明書の有効期限が切れています
|
||||||
validateSignature.cert.revoked=Certificate has been revoked
|
validateSignature.cert.revoked=証明書は取り消されました
|
||||||
validateSignature.signature.info=Signature Information
|
validateSignature.signature.info=署名情報
|
||||||
validateSignature.signature=Signature
|
validateSignature.signature=署名
|
||||||
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
|
validateSignature.signature.mathValid=署名は数学的には有効ですが:
|
||||||
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
|
validateSignature.selectCustomCert=カスタム証明書ファイル X.509 (オプション)
|
||||||
validateSignature.cert.info=Certificate Details
|
validateSignature.cert.info=証明書の詳細
|
||||||
validateSignature.cert.issuer=Issuer
|
validateSignature.cert.issuer=発行者
|
||||||
validateSignature.cert.subject=Subject
|
validateSignature.cert.subject=主題
|
||||||
validateSignature.cert.serialNumber=Serial Number
|
validateSignature.cert.serialNumber=シリアルナンバー
|
||||||
validateSignature.cert.validFrom=Valid From
|
validateSignature.cert.validFrom=有効開始日
|
||||||
validateSignature.cert.validUntil=Valid Until
|
validateSignature.cert.validUntil=有効期限
|
||||||
validateSignature.cert.algorithm=Algorithm
|
validateSignature.cert.algorithm=アルゴリズム
|
||||||
validateSignature.cert.keySize=Key Size
|
validateSignature.cert.keySize=キーサイズ
|
||||||
validateSignature.cert.version=Version
|
validateSignature.cert.version=バージョン
|
||||||
validateSignature.cert.keyUsage=Key Usage
|
validateSignature.cert.keyUsage=キーの使用法
|
||||||
validateSignature.cert.selfSigned=Self-Signed
|
validateSignature.cert.selfSigned=自己署名
|
||||||
validateSignature.cert.bits=bits
|
validateSignature.cert.bits=ビット
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Página(s) Selecionadas
|
|||||||
multiTool.undo=Desfazer
|
multiTool.undo=Desfazer
|
||||||
multiTool.redo=Refazer
|
multiTool.redo=Refazer
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=Esta função também está disponível em <a href="{0}">Multiferramentas de PDF</a>. Com uma interface mais completa e funções adicionais.
|
multiTool-advert.message=Esta função também está disponível em <a href="{0}">Multiferramentas de PDF</a>. Com uma interface mais completa e funções adicionais.
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#decrypt
|
||||||
|
decrypt.passwordPrompt=This file is password-protected. Please enter the password:
|
||||||
|
decrypt.cancelled=Operation cancelled for PDF: {0}
|
||||||
|
decrypt.noPassword=No password provided for encrypted PDF: {0}
|
||||||
|
decrypt.invalidPassword=Please try again with the correct password.
|
||||||
|
decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
|
||||||
|
decrypt.unexpectedError=There was an error processing the file. Please try again.
|
||||||
|
decrypt.serverError=Server error while decrypting: {0}
|
||||||
|
decrypt.success=File decrypted successfully.
|
||||||
|
|
||||||
#multiTool-advert
|
#multiTool-advert
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
security:
|
security:
|
||||||
enableLogin: false # set to 'true' to enable login
|
enableLogin: false # set to 'true' to enable login
|
||||||
csrfDisabled: true # set to 'true' to disable CSRF protection (not recommended for production)
|
csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production)
|
||||||
loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1
|
loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1
|
||||||
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
|
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
|
||||||
loginMethod: all # Accepts values like 'all' and 'normal'(only Login with Username/Password), 'oauth2'(only Login with OAuth2) or 'saml2'(only Login with SAML2)
|
loginMethod: all # Accepts values like 'all' and 'normal'(only Login with Username/Password), 'oauth2'(only Login with OAuth2) or 'saml2'(only Login with SAML2)
|
||||||
@ -102,6 +102,7 @@ metrics:
|
|||||||
AutomaticallyGenerated:
|
AutomaticallyGenerated:
|
||||||
key: example
|
key: example
|
||||||
UUID: example
|
UUID: example
|
||||||
|
appVersion: 0.35.0
|
||||||
|
|
||||||
processExecutor:
|
processExecutor:
|
||||||
sessionLimit: # Process executor instances limits
|
sessionLimit: # Process executor instances limits
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: -20px;
|
margin: -20px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
box-sizing:content-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.feature-group-container.animated-group {
|
.feature-group-container.animated-group {
|
||||||
|
BIN
src/main/resources/static/favicon.icns
Normal file
BIN
src/main/resources/static/favicon.icns
Normal file
Binary file not shown.
@ -268,7 +268,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
const parent = header.parentNode;
|
const parent = header.parentNode;
|
||||||
const container = header.parentNode.querySelector(".feature-group-container");
|
const container = header.parentNode.querySelector(".feature-group-container");
|
||||||
if (parent.id !== "groupFavorites") {
|
if (parent.id !== "groupFavorites") {
|
||||||
container.style.maxHeight = container.clientHeight + "px";
|
container.style.maxHeight = container.scrollHeight + "px";
|
||||||
}
|
}
|
||||||
header.onclick = () => {
|
header.onclick = () => {
|
||||||
expandCollapseToggle(parent);
|
expandCollapseToggle(parent);
|
||||||
|
2
test.sh
2
test.sh
@ -104,7 +104,7 @@ main() {
|
|||||||
# run_tests "Stirling-PDF-Security" "./exampleYmlFiles/docker-compose-latest-security.yml"
|
# run_tests "Stirling-PDF-Security" "./exampleYmlFiles/docker-compose-latest-security.yml"
|
||||||
# docker-compose -f "./exampleYmlFiles/docker-compose-latest-security.yml" down
|
# docker-compose -f "./exampleYmlFiles/docker-compose-latest-security.yml" down
|
||||||
|
|
||||||
run_tests "Stirling-PDF-Security-Fat" "./exampleYmlFiles/docker-compose-latest-fat-security.yml"
|
run_tests "Stirling-PDF-Security-Fat" "./exampleYmlFiles/test_cicd.yml"
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
cd cucumber
|
cd cucumber
|
||||||
if python -m behave; then
|
if python -m behave; then
|
||||||
|
Loading…
x
Reference in New Issue
Block a user