Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01b2613efe | ||
|
|
50fc13b30c | ||
|
|
b7dc248f93 | ||
|
|
8b05204047 | ||
|
|
18680f2847 | ||
|
|
6c790299aa | ||
|
|
65d662588e | ||
|
|
ab7acb5db3 | ||
|
|
dde0f5cd10 | ||
|
|
5d70217961 | ||
|
|
a0f0a446de | ||
|
|
cd6f3862f6 | ||
|
|
0f4eb8398a | ||
|
|
c9a3f48e5a | ||
|
|
a7f67961e7 | ||
|
|
32209534a0 | ||
|
|
7196f0f970 | ||
|
|
64d0be5ffa | ||
|
|
31be5baf3d | ||
|
|
4781fd515b | ||
|
|
11497f52d4 | ||
|
|
fa934f06ab | ||
|
|
3d78e01559 | ||
|
|
65f9438639 | ||
|
|
6ffa80c386 | ||
|
|
8eb7b18089 | ||
|
|
9041441c46 | ||
|
|
502a4b1cc3 | ||
|
|
ce13648075 | ||
|
|
9644557a9e | ||
|
|
01964add79 | ||
|
|
822e771f45 | ||
|
|
c0888fb938 | ||
|
|
5cdb3bee21 | ||
|
|
4cce6c1c21 | ||
|
|
b928e294d1 | ||
|
|
ec3aa17f65 | ||
|
|
851d77de8e | ||
|
|
137fdaca6a | ||
|
|
7371f4e87f | ||
|
|
6529eb6b61 | ||
|
|
729af56d1b | ||
|
|
9f4a600eba | ||
|
|
ddb2528ecf | ||
|
|
eb5aeb4595 | ||
|
|
b93bff5cad | ||
|
|
3ae891c62e | ||
|
|
48bd060d6e | ||
|
|
5dee64ab7b | ||
|
|
a2f66493ea | ||
|
|
37a0103699 | ||
|
|
2dabf8955d | ||
|
|
9ab471fb63 | ||
|
|
801fd8bb21 | ||
|
|
5e9c780d31 | ||
|
|
bbaaaf7ae6 | ||
|
|
befd0974f3 | ||
|
|
250c317155 | ||
|
|
2c148eb0c0 | ||
|
|
ead8010bd1 | ||
|
|
0962159523 | ||
|
|
cbb4ccd4b7 | ||
|
|
41e73e4fd1 | ||
|
|
86a6ea5a26 | ||
|
|
ce5af5ddde | ||
|
|
0d193cd235 | ||
|
|
f06d755899 | ||
|
|
4dcf2f5870 | ||
|
|
c2179ccd63 | ||
|
|
94445bceb1 | ||
|
|
7b49d85804 | ||
|
|
4190aa20a6 | ||
|
|
5a832198b4 | ||
|
|
2066bb2ae8 | ||
|
|
5d64c97406 | ||
|
|
d648c6d4b4 | ||
|
|
435bfa3b3f | ||
|
|
4d0135d7b7 | ||
|
|
5975928e89 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -10,4 +10,4 @@ liberapay: # Replace with a single Liberapay username
|
|||||||
issuehunt: # Replace with a single IssueHunt username
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
otechie: # Replace with a single Otechie username
|
otechie: # Replace with a single Otechie username
|
||||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||||
custom: ['https://paypal.me/froodleplex?country.x=GB&locale.x=en_GB'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
custom: ['https://www.paypal.com/donate/?hosted_button_id=MN7JPG5G6G3JL'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||||
|
|||||||
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -3,14 +3,8 @@ name: "Build repo"
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
paths-ignore:
|
|
||||||
- ".github/**"
|
|
||||||
- "**/*.md"
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
paths-ignore:
|
|
||||||
- ".github/**"
|
|
||||||
- "**/*.md"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -36,7 +30,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: gradle/actions/setup-gradle@v3
|
- uses: gradle/actions/setup-gradle@v3
|
||||||
with:
|
with:
|
||||||
gradle-version: 7.6
|
gradle-version: 8.7
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew build --no-build-cache
|
run: ./gradlew build --no-build-cache
|
||||||
|
|||||||
2
.github/workflows/push-docker.yml
vendored
2
.github/workflows/push-docker.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: gradle/actions/setup-gradle@v3
|
- uses: gradle/actions/setup-gradle@v3
|
||||||
with:
|
with:
|
||||||
gradle-version: 7.6
|
gradle-version: 8.7
|
||||||
|
|
||||||
- name: Run Gradle Command
|
- name: Run Gradle Command
|
||||||
run: ./gradlew clean build
|
run: ./gradlew clean build
|
||||||
|
|||||||
2
.github/workflows/releaseArtifacts.yml
vendored
2
.github/workflows/releaseArtifacts.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: gradle/actions/setup-gradle@v3
|
- uses: gradle/actions/setup-gradle@v3
|
||||||
with:
|
with:
|
||||||
gradle-version: 7.6
|
gradle-version: 8.7
|
||||||
|
|
||||||
- name: Generate jar (With Security=${{ matrix.enable_security }})
|
- name: Generate jar (With Security=${{ matrix.enable_security }})
|
||||||
run: ./gradlew clean createExe
|
run: ./gradlew clean createExe
|
||||||
|
|||||||
9
.github/workflows/test.yml
vendored
9
.github/workflows/test.yml
vendored
@@ -32,6 +32,15 @@ jobs:
|
|||||||
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.26.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.26.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||||
# sudo chmod +x /usr/local/bin/docker-compose
|
# sudo chmod +x /usr/local/bin/docker-compose
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.7"
|
||||||
|
|
||||||
|
- name: Pip requirements
|
||||||
|
run: |
|
||||||
|
pip install -r ./cucumber/requirements.txt
|
||||||
|
|
||||||
- name: Run Docker Compose Tests
|
- name: Run Docker Compose Tests
|
||||||
run: |
|
run: |
|
||||||
chmod +x ./test.sh
|
chmod +x ./test.sh
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -125,3 +125,6 @@ watchedFolders/
|
|||||||
# Ignore Mac DS_Store files
|
# Ignore Mac DS_Store files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
|
|
||||||
|
#cucumber
|
||||||
|
/cucumber/reports/**
|
||||||
@@ -29,7 +29,7 @@ If you would like to add or modify a translation, please see [How to add new lan
|
|||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
|
|
||||||
Documentation for Stirling-PDF is handled in a seperate repository. Please see [Docs repository](https://github.com/Stirling-Tools/Stirling-Tools.github.io) or use "edit this page"-button at the bottom of each page at [https://stirlingtools.com/docs/](https://stirlingtools.com/docs/).
|
Documentation for Stirling-PDF is handled in a separate repository. Please see [Docs repository](https://github.com/Stirling-Tools/Stirling-Tools.github.io) or use "edit this page"-button at the bottom of each page at [https://stirlingtools.com/docs/](https://stirlingtools.com/docs/).
|
||||||
|
|
||||||
## Fixing Bugs or Adding a New Feature
|
## Fixing Bugs or Adding a New Feature
|
||||||
|
|
||||||
|
|||||||
25
Dockerfile
25
Dockerfile
@@ -1,5 +1,5 @@
|
|||||||
# Main stage
|
# Main stage
|
||||||
FROM alpine:20240329
|
FROM alpine:3.20.0
|
||||||
|
|
||||||
# Copy necessary files
|
# Copy necessary files
|
||||||
COPY scripts /scripts
|
COPY scripts /scripts
|
||||||
@@ -10,35 +10,33 @@ COPY build/libs/*.jar app.jar
|
|||||||
|
|
||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
|
|
||||||
# Set Environment Variables
|
# Set Environment Variables
|
||||||
ENV DOCKER_ENABLE_SECURITY=false \
|
ENV DOCKER_ENABLE_SECURITY=false \
|
||||||
VERSION_TAG=$VERSION_TAG \
|
VERSION_TAG=$VERSION_TAG \
|
||||||
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" \
|
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" \
|
||||||
HOME=/home/stirlingpdfuser \
|
HOME=/home/stirlingpdfuser \
|
||||||
PUID=1000 \
|
PUID=1000 \
|
||||||
PGID=1000 \
|
PGID=1000 \
|
||||||
UMASK=022
|
UMASK=022
|
||||||
|
|
||||||
|
|
||||||
# JDK for app
|
# JDK for app
|
||||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||||
apk update && \
|
apk upgrade --no-cache -a && \
|
||||||
apk add --no-cache \
|
apk add --no-cache \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
tzdata \
|
tzdata \
|
||||||
tini \
|
tini \
|
||||||
openssl \
|
|
||||||
openssl-dev \
|
|
||||||
bash \
|
bash \
|
||||||
curl \
|
curl \
|
||||||
openjdk21-jre \
|
|
||||||
su-exec \
|
|
||||||
shadow \
|
shadow \
|
||||||
|
su-exec \
|
||||||
|
openssl \
|
||||||
|
openssl-dev \
|
||||||
|
openjdk21-jre \
|
||||||
# Doc conversion
|
# Doc conversion
|
||||||
libreoffice@testing \
|
libreoffice \
|
||||||
# pdftohtml
|
# pdftohtml
|
||||||
poppler-utils \
|
poppler-utils \
|
||||||
# OCR MY PDF (unpaper for descew and other advanced featues)
|
# OCR MY PDF (unpaper for descew and other advanced featues)
|
||||||
@@ -60,10 +58,9 @@ openssl-dev \
|
|||||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
|
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
|
||||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar && \
|
chown stirlingpdfuser:stirlingpdfgroup /app.jar && \
|
||||||
tesseract --list-langs && \
|
tesseract --list-langs
|
||||||
rm -rf /var/cache/apk/*
|
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080/tcp
|
||||||
|
|
||||||
# Set user and run command
|
# Set user and run command
|
||||||
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# use alpine
|
# use alpine
|
||||||
FROM alpine:3.19.1
|
FROM alpine:3.20.0
|
||||||
|
|
||||||
ARG VERSION_TAG
|
ARG VERSION_TAG
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ ENV DOCKER_ENABLE_SECURITY=false \
|
|||||||
HOME=/home/stirlingpdfuser \
|
HOME=/home/stirlingpdfuser \
|
||||||
VERSION_TAG=$VERSION_TAG \
|
VERSION_TAG=$VERSION_TAG \
|
||||||
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" \
|
JAVA_TOOL_OPTIONS="$JAVA_TOOL_OPTIONS -XX:MaxRAMPercentage=75" \
|
||||||
PUID=1000 \
|
PUID=1000 \
|
||||||
PGID=1000 \
|
PGID=1000 \
|
||||||
UMASK=022
|
UMASK=022
|
||||||
|
|
||||||
@@ -18,24 +18,23 @@ COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
|||||||
COPY pipeline /pipeline
|
COPY pipeline /pipeline
|
||||||
COPY build/libs/*.jar app.jar
|
COPY build/libs/*.jar app.jar
|
||||||
|
|
||||||
|
|
||||||
# Set up necessary directories and permissions
|
# Set up necessary directories and permissions
|
||||||
|
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||||
RUN mkdir /configs /logs /customFiles && \
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||||
chmod +x /scripts/*.sh && \
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||||
|
apk upgrade --no-cache -a && \
|
||||||
apk add --no-cache \
|
apk add --no-cache \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
tzdata \
|
tzdata \
|
||||||
tini \
|
tini \
|
||||||
bash \
|
bash \
|
||||||
curl \
|
curl \
|
||||||
su-exec \
|
|
||||||
shadow \
|
shadow \
|
||||||
|
su-exec \
|
||||||
openjdk21-jre && \
|
openjdk21-jre && \
|
||||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
|
||||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
|
||||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
|
||||||
# User permissions
|
# User permissions
|
||||||
|
mkdir /configs /logs /customFiles && \
|
||||||
|
chmod +x /scripts/*.sh && \
|
||||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline && \
|
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline && \
|
||||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||||
@@ -43,9 +42,8 @@ RUN mkdir /configs /logs /customFiles && \
|
|||||||
# Set environment variables
|
# Set environment variables
|
||||||
ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI
|
ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080/tcp
|
||||||
|
|
||||||
ENTRYPOINT ["tini", "--", "/scripts/init-without-ocr.sh"]
|
|
||||||
|
|
||||||
# Run the application
|
# Run the application
|
||||||
|
ENTRYPOINT ["tini", "--", "/scripts/init-without-ocr.sh"]
|
||||||
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]
|
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]
|
||||||
|
|||||||
50
README.md
50
README.md
@@ -159,38 +159,39 @@ Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR
|
|||||||
|
|
||||||
## Supported Languages
|
## Supported Languages
|
||||||
|
|
||||||
Stirling PDF currently supports 27!
|
Stirling PDF currently supports 28!
|
||||||
|
|
||||||
| Language | Progress |
|
| Language | Progress |
|
||||||
| ------------------------------------------- | -------------------------------------- |
|
| ------------------------------------------- | -------------------------------------- |
|
||||||
| English (English) (en_GB) |  |
|
| English (English) (en_GB) |  |
|
||||||
| English (US) (en_US) |  |
|
| English (US) (en_US) |  |
|
||||||
| Arabic (العربية) (ar_AR) |  |
|
| Arabic (العربية) (ar_AR) |  |
|
||||||
| German (Deutsch) (de_DE) |  |
|
| German (Deutsch) (de_DE) |  |
|
||||||
| French (Français) (fr_FR) |  |
|
| French (Français) (fr_FR) |  |
|
||||||
| Spanish (Español) (es_ES) |  |
|
| Spanish (Español) (es_ES) |  |
|
||||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||||
| Catalan (Català) (ca_CA) |  |
|
| Catalan (Català) (ca_CA) |  |
|
||||||
| Italian (Italiano) (it_IT) |  |
|
| Italian (Italiano) (it_IT) |  |
|
||||||
| Swedish (Svenska) (sv_SE) |  |
|
| Swedish (Svenska) (sv_SE) |  |
|
||||||
| Polish (Polski) (pl_PL) |  |
|
| Polish (Polski) (pl_PL) |  |
|
||||||
| Romanian (Română) (ro_RO) |  |
|
| Romanian (Română) (ro_RO) |  |
|
||||||
| Korean (한국어) (ko_KR) |  |
|
| Korean (한국어) (ko_KR) |  |
|
||||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||||
| Russian (Русский) (ru_RU) |  |
|
| Russian (Русский) (ru_RU) |  |
|
||||||
| Basque (Euskara) (eu_ES) |  |
|
| Basque (Euskara) (eu_ES) |  |
|
||||||
| Japanese (日本語) (ja_JP) |  |
|
| Japanese (日本語) (ja_JP) |  |
|
||||||
| Dutch (Nederlands) (nl_NL) |  |
|
| Dutch (Nederlands) (nl_NL) |  |
|
||||||
| Greek (Ελληνικά) (el_GR) |  |
|
| Greek (Ελληνικά) (el_GR) |  |
|
||||||
| Turkish (Türkçe) (tr_TR) |  |
|
| Turkish (Türkçe) (tr_TR) |  |
|
||||||
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
||||||
| Hindi (हिंदी) (hi_IN) |  |
|
| Hindi (हिंदी) (hi_IN) |  |
|
||||||
| Hungarian (Magyar) (hu_HU) |  |
|
| Hungarian (Magyar) (hu_HU) |  |
|
||||||
| Bulgarian (Български) (bg_BG) |  |
|
| Bulgarian (Български) (bg_BG) |  |
|
||||||
| Sebian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
| Sebian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||||
| Ukrainian (Українська) (uk_UA) |  |
|
| Ukrainian (Українська) (uk_UA) |  |
|
||||||
| Slovakian (Slovensky) (sk_SK) |  |
|
| Slovakian (Slovensky) (sk_SK) |  |
|
||||||
|
| Czech (Česky) (cs_CZ) |  |
|
||||||
|
|
||||||
## Contributing (creating issues, translations, fixing bugs, etc.)
|
## Contributing (creating issues, translations, fixing bugs, etc.)
|
||||||
|
|
||||||
@@ -226,7 +227,7 @@ security:
|
|||||||
loginAttemptCount: 5 # lock user account after 5 tries
|
loginAttemptCount: 5 # lock user account after 5 tries
|
||||||
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
|
loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts
|
||||||
# initialLogin:
|
# initialLogin:
|
||||||
# username: "admin" # Initial username for the first login (these are defaulted)
|
# username: "admin" # Initial username for the first login
|
||||||
# password: "stirling" # Initial password for the first login
|
# password: "stirling" # Initial password for the first login
|
||||||
# oauth2:
|
# oauth2:
|
||||||
# enabled: false # set to 'true' to enable login (Note: enableLogin must also be 'true' for this to work)
|
# enabled: false # set to 'true' to enable login (Note: enableLogin must also be 'true' for this to work)
|
||||||
@@ -237,6 +238,23 @@ security:
|
|||||||
# useAsUsername: "email" # Default is 'email'; custom fields can be used as the username
|
# useAsUsername: "email" # Default is 'email'; custom fields can be used as the username
|
||||||
# scopes: "openid, profile, email" # Specify the scopes for which the application will request permissions
|
# scopes: "openid, profile, email" # Specify the scopes for which the application will request permissions
|
||||||
# provider: "google" # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
# provider: "google" # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
||||||
|
# client:
|
||||||
|
# google:
|
||||||
|
# clientId: "" # Client ID for Google OAuth2
|
||||||
|
# clientSecret: "" # Client Secret for Google OAuth2
|
||||||
|
# scopes: "https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile" # Scopes for Google OAuth2
|
||||||
|
# useAsUsername: "email" # Field to use as the username for Google OAuth2
|
||||||
|
# github:
|
||||||
|
# clientId: "" # Client ID for GitHub OAuth2
|
||||||
|
# clientSecret: "" # Client Secret for GitHub OAuth2
|
||||||
|
# scopes: "read:user" # Scope for GitHub OAuth2
|
||||||
|
# useAsUsername: "login" # Field to use as the username for GitHub OAuth2
|
||||||
|
# keycloak:
|
||||||
|
# issuer: "http://192.168.0.123:8888/realms/stirling-pdf" # URL of the Keycloak realm's OpenID Connect Discovery endpoint
|
||||||
|
# clientId: "stirling-pdf" # Client ID for Keycloak OAuth2
|
||||||
|
# clientSecret: "" # Client Secret for Keycloak OAuth2
|
||||||
|
# scopes: "openid, profile, email" # Scopes for Keycloak OAuth2
|
||||||
|
# useAsUsername: "email" # Field to use as the username for Keycloak OAuth2
|
||||||
|
|
||||||
system:
|
system:
|
||||||
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
||||||
|
|||||||
10
build.gradle
10
build.gradle
@@ -12,7 +12,7 @@ plugins {
|
|||||||
import com.github.jk1.license.render.*
|
import com.github.jk1.license.render.*
|
||||||
|
|
||||||
group = 'stirling.software'
|
group = 'stirling.software'
|
||||||
version = '0.24.5'
|
version = '0.25.0'
|
||||||
|
|
||||||
//17 is lowest but we support and recommend 21
|
//17 is lowest but we support and recommend 21
|
||||||
sourceCompatibility = '17'
|
sourceCompatibility = '17'
|
||||||
@@ -94,7 +94,13 @@ dependencies {
|
|||||||
implementation("io.github.pixee:java-security-toolkit:1.1.3")
|
implementation("io.github.pixee:java-security-toolkit:1.1.3")
|
||||||
|
|
||||||
implementation 'org.yaml:snakeyaml:2.2'
|
implementation 'org.yaml:snakeyaml:2.2'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web:3.2.4'
|
|
||||||
|
// Exclude Tomcat and include Jetty
|
||||||
|
implementation('org.springframework.boot:spring-boot-starter-web:3.2.4') {
|
||||||
|
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
|
||||||
|
}
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-jetty:3.2.4'
|
||||||
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.2.4'
|
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.2.4'
|
||||||
|
|
||||||
if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') {
|
if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
apiVersion: v2
|
apiVersion: v2
|
||||||
appVersion: 0.24.5
|
appVersion: 0.25.0
|
||||||
description: locally hosted web application that allows you to perform various operations
|
description: locally hosted web application that allows you to perform various operations
|
||||||
on PDF files
|
on PDF files
|
||||||
home: https://github.com/Stirling-Tools/Stirling-PDF
|
home: https://github.com/Stirling-Tools/Stirling-PDF
|
||||||
|
|||||||
BIN
cucumber/exampleFiles/example.docx
Normal file
BIN
cucumber/exampleFiles/example.docx
Normal file
Binary file not shown.
BIN
cucumber/exampleFiles/example.odp
Normal file
BIN
cucumber/exampleFiles/example.odp
Normal file
Binary file not shown.
BIN
cucumber/exampleFiles/example.odt
Normal file
BIN
cucumber/exampleFiles/example.odt
Normal file
Binary file not shown.
BIN
cucumber/exampleFiles/example.pptx
Normal file
BIN
cucumber/exampleFiles/example.pptx
Normal file
Binary file not shown.
158
cucumber/exampleFiles/example.rtf
Normal file
158
cucumber/exampleFiles/example.rtf
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
{\rtf1\ansi\ansicpg1252\uc0\stshfdbch0\stshfloch0\stshfhich0\stshfbi0\deff0\adeff0{\fonttbl{\f0\froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f1\froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol;}{\f2\fswiss\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial;}}{\colortbl;\red0\green0\blue0;\red67\green67\blue67;
|
||||||
|
\red102\green102\blue102;}{\stylesheet{\s0\snext0\sqformat\spriority0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 Normal;}{\s1\sbasedon0\snext0\styrsid15694742
|
||||||
|
\sqformat\spriority0\keep\keepn\fi0\sb400\sa120\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs40\ltrch\b0\i0\fs40\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 heading 1;}{\s2\sbasedon0\snext0\styrsid15694742
|
||||||
|
\sqformat\spriority0\keep\keepn\fi0\sb360\sa120\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs32\ltrch\b0\i0\fs32\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 heading 2;}{\s3\sbasedon0\snext0\styrsid15694742
|
||||||
|
\sqformat\spriority0\keep\keepn\fi0\sb320\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs28\ltrch\b0\i0\fs28\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf2 heading 3;}{\s4\sbasedon0\snext0\styrsid15694742
|
||||||
|
\sqformat\spriority0\keep\keepn\fi0\sb280\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs24\ltrch\b0\i0\fs24\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 4;}{\s5\sbasedon0\snext0\styrsid15694742
|
||||||
|
\sqformat\spriority0\keep\keepn\fi0\sb240\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 5;}{\s6\sbasedon0\snext0\styrsid15694742
|
||||||
|
\sqformat\spriority0\keep\keepn\fi0\sb240\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai\af2\afs22\ltrch\b0\i\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 6;}{\*\cs10\additive\ssemihidden\spriority0 Default Paragraph Font;
|
||||||
|
}{\*\ts11\tsrowd\snext11\ssemihidden\spriority0\aspalpha\aspnum\adjustright\ltrpar\li0\lin0\ri0\rin0\ql\faauto\tsvertalt\tsbrdrl\tsbrdrr\tsbrdrt\tsbrdrb\tsbrdrdgr\tsbrdrdgl\tsbrdrh\tsbrdrv\trpaddl108\trpaddfl3\trwWidthB0\trftsWidthB3\trpaddt0\trpaddft3\trpaddb0
|
||||||
|
\trpaddfb3\trpaddr108\trpaddfr3 Normal Table;}{\s15\sbasedon0\snext15\styrsid15694742\sqformat\spriority0\keep\keepn\fi0\sb0\sa60\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs52\ltrch\b0\i0\fs52\loch\af2
|
||||||
|
\dbch\af2\hich\f2\strike0\ulnone\cf1 Title;}{\s16\sbasedon0\snext16\styrsid15694742\sqformat\spriority0\keep\keepn\fi0\sb0\sa320\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs30\ltrch\b0\i0\fs30
|
||||||
|
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 Subtitle;}}{\*\rsidtbl\rsid10976062\rsid13249109}{\*\generator Aspose.Words for Java 23.4.0;}{\info\version1\edmins0\nofpages1\nofwords0\nofchars0\nofcharsws0}\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0{
|
||||||
|
\mmathPr\mbrkBin0\mbrkBinSub0\mdefJc1\mdispDef1\minterSp0\mintLim0\mintraSp0\mlMargin0\mmathFont0\mnaryLim1\mpostSp0\mpreSp0\mrMargin0\msmallFrac0\mwrapIndent1440\mwrapRight0}\deflang1033\deflangfe2052\adeflang1025\jexpand\showxmlerrors1\validatexml1{
|
||||||
|
\*\wgrffmtfilter 013f}\viewkind1\viewscale100\fet0\ftnbj\aenddoc\ftnrstcont\aftnrstcont\ftnnar\aftnnrlc\widowctrl\nospaceforul\nolnhtadjtbl\alntblind\lyttblrtgr\dntblnsbdb\noxlattoyen\wrppunct\nobrkwrptbl\expshrtn\snaptogridincell\asianbrkrule\htmautsp\noultrlspc
|
||||||
|
\useltbaln\splytwnine\ftnlytwnine\lytcalctblwd\allowfieldendsel\lnbrkrule\nouicompat\nofeaturethrottle1\utinl\formshade\nojkernpunct\dghspace180\dgvspace180\dghorigin1800\dgvorigin1440\dghshow1\dgvshow1\dgmargin\pgbrdrhead\pgbrdrfoot\rsidroot10976062\sectd\sectlinegrid360\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\guttersxn0\headery720\footery720\colsx720\ltrsect\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar
|
||||||
|
\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\alang1025\afs22\ltrch\b0\i0\fs22\lang1033\langnp1033\langfe1033\langfenp1033\loch\af2\dbch\af2
|
||||||
|
\hich\f2\strike0\ulnone\cf1 A}{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar
|
||||||
|
\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||||
|
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||||
|
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||||
|
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||||
|
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||||
|
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||||
|
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||||
|
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||||
|
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||||
|
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||||
|
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||||
|
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||||
|
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||||
|
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||||
|
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||||
|
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||||
|
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||||
|
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||||
|
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||||
|
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||||
|
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||||
|
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||||
|
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||||
|
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||||
|
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||||
|
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||||
|
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||||
|
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||||
|
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||||
|
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||||
|
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||||
|
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||||
|
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||||
|
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||||
|
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||||
|
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||||
|
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||||
|
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||||
|
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||||
|
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||||
|
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||||
|
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||||
|
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||||
|
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||||
|
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||||
|
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||||
|
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||||
|
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||||
|
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||||
|
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\alang1025\afs22\ltrch\b0\i0\fs22\lang1033\langnp1033\langfe1033\langfenp1033\loch\af2
|
||||||
|
\dbch\af2\hich\f2\strike0\ulnone\cf1 B}{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar
|
||||||
|
\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard
|
||||||
|
\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||||
|
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||||
|
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||||
|
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||||
|
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||||
|
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||||
|
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||||
|
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||||
|
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||||
|
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||||
|
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||||
|
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||||
|
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||||
|
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||||
|
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||||
|
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||||
|
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||||
|
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||||
|
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||||
|
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||||
|
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||||
|
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||||
|
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||||
|
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||||
|
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||||
|
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||||
|
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||||
|
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||||
|
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||||
|
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||||
|
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||||
|
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||||
|
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||||
|
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||||
|
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||||
|
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||||
|
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||||
|
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0
|
||||||
|
\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0
|
||||||
|
\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0
|
||||||
|
\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0
|
||||||
|
\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2
|
||||||
|
\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22
|
||||||
|
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw
|
||||||
|
\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}
|
||||||
|
\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22
|
||||||
|
\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb
|
||||||
|
\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\alang1025\afs22\ltrch\b0\i0\fs22\lang1033\langnp1033\langfe1033\langfenp1033
|
||||||
|
\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 C}{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}{
|
||||||
|
\*\latentstyles\lsdstimax267\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef0{\lsdlockedexcept\lsdqformat1 Normal;\lsdqformat1 heading 1;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 2;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 3;
|
||||||
|
\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 4;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 5;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 6;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 7;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 8;
|
||||||
|
\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 9;\lsdsemihidden1\lsdunhideused1\lsdqformat1 caption;\lsdqformat1 Title;\lsdqformat1 Subtitle;\lsdqformat1 Strong;\lsdqformat1 Emphasis;\lsdsemihidden1\lsdpriority99 Placeholder Text;\lsdqformat1\lsdpriority1 No Spacing;
|
||||||
|
\lsdpriority60 Light Shading;\lsdpriority61 Light List;\lsdpriority62 Light Grid;\lsdpriority63 Medium Shading 1;\lsdpriority64 Medium Shading 2;\lsdpriority65 Medium List 1;\lsdpriority66 Medium List 2;\lsdpriority67 Medium Grid 1;\lsdpriority68 Medium Grid 2;
|
||||||
|
\lsdpriority69 Medium Grid 3;\lsdpriority70 Dark List;\lsdpriority71 Colorful Shading;\lsdpriority72 Colorful List;\lsdpriority73 Colorful Grid;\lsdpriority60 Light Shading Accent 1;\lsdpriority61 Light List Accent 1;\lsdpriority62 Light Grid Accent 1;\lsdpriority63 Medium Shading 1 Accent 1;
|
||||||
|
\lsdpriority64 Medium Shading 2 Accent 1;\lsdpriority65 Medium List 1 Accent 1;\lsdsemihidden1\lsdpriority99 Revision;\lsdqformat1\lsdpriority34 List Paragraph;\lsdqformat1\lsdpriority29 Quote;\lsdqformat1\lsdpriority30 Intense Quote;\lsdpriority66 Medium List 2 Accent 1;
|
||||||
|
\lsdpriority67 Medium Grid 1 Accent 1;\lsdpriority68 Medium Grid 2 Accent 1;\lsdpriority69 Medium Grid 3 Accent 1;\lsdpriority70 Dark List Accent 1;\lsdpriority71 Colorful Shading Accent 1;\lsdpriority72 Colorful List Accent 1;\lsdpriority73 Colorful Grid Accent 1;
|
||||||
|
\lsdpriority60 Light Shading Accent 2;\lsdpriority61 Light List Accent 2;\lsdpriority62 Light Grid Accent 2;\lsdpriority63 Medium Shading 1 Accent 2;\lsdpriority64 Medium Shading 2 Accent 2;\lsdpriority65 Medium List 1 Accent 2;\lsdpriority66 Medium List 2 Accent 2;
|
||||||
|
\lsdpriority67 Medium Grid 1 Accent 2;\lsdpriority68 Medium Grid 2 Accent 2;\lsdpriority69 Medium Grid 3 Accent 2;\lsdpriority70 Dark List Accent 2;\lsdpriority71 Colorful Shading Accent 2;\lsdpriority72 Colorful List Accent 2;\lsdpriority73 Colorful Grid Accent 2;
|
||||||
|
\lsdpriority60 Light Shading Accent 3;\lsdpriority61 Light List Accent 3;\lsdpriority62 Light Grid Accent 3;\lsdpriority63 Medium Shading 1 Accent 3;\lsdpriority64 Medium Shading 2 Accent 3;\lsdpriority65 Medium List 1 Accent 3;\lsdpriority66 Medium List 2 Accent 3;
|
||||||
|
\lsdpriority67 Medium Grid 1 Accent 3;\lsdpriority68 Medium Grid 2 Accent 3;\lsdpriority69 Medium Grid 3 Accent 3;\lsdpriority70 Dark List Accent 3;\lsdpriority71 Colorful Shading Accent 3;\lsdpriority72 Colorful List Accent 3;\lsdpriority73 Colorful Grid Accent 3;
|
||||||
|
\lsdpriority60 Light Shading Accent 4;\lsdpriority61 Light List Accent 4;\lsdpriority62 Light Grid Accent 4;\lsdpriority63 Medium Shading 1 Accent 4;\lsdpriority64 Medium Shading 2 Accent 4;\lsdpriority65 Medium List 1 Accent 4;\lsdpriority66 Medium List 2 Accent 4;
|
||||||
|
\lsdpriority67 Medium Grid 1 Accent 4;\lsdpriority68 Medium Grid 2 Accent 4;\lsdpriority69 Medium Grid 3 Accent 4;\lsdpriority70 Dark List Accent 4;\lsdpriority71 Colorful Shading Accent 4;\lsdpriority72 Colorful List Accent 4;\lsdpriority73 Colorful Grid Accent 4;
|
||||||
|
\lsdpriority60 Light Shading Accent 5;\lsdpriority61 Light List Accent 5;\lsdpriority62 Light Grid Accent 5;\lsdpriority63 Medium Shading 1 Accent 5;\lsdpriority64 Medium Shading 2 Accent 5;\lsdpriority65 Medium List 1 Accent 5;\lsdpriority66 Medium List 2 Accent 5;
|
||||||
|
\lsdpriority67 Medium Grid 1 Accent 5;\lsdpriority68 Medium Grid 2 Accent 5;\lsdpriority69 Medium Grid 3 Accent 5;\lsdpriority70 Dark List Accent 5;\lsdpriority71 Colorful Shading Accent 5;\lsdpriority72 Colorful List Accent 5;\lsdpriority73 Colorful Grid Accent 5;
|
||||||
|
\lsdpriority60 Light Shading Accent 6;\lsdpriority61 Light List Accent 6;\lsdpriority62 Light Grid Accent 6;\lsdpriority63 Medium Shading 1 Accent 6;\lsdpriority64 Medium Shading 2 Accent 6;\lsdpriority65 Medium List 1 Accent 6;\lsdpriority66 Medium List 2 Accent 6;
|
||||||
|
\lsdpriority67 Medium Grid 1 Accent 6;\lsdpriority68 Medium Grid 2 Accent 6;\lsdpriority69 Medium Grid 3 Accent 6;\lsdpriority70 Dark List Accent 6;\lsdpriority71 Colorful Shading Accent 6;\lsdpriority72 Colorful List Accent 6;\lsdpriority73 Colorful Grid Accent 6;
|
||||||
|
\lsdqformat1\lsdpriority19 Subtle Emphasis;\lsdqformat1\lsdpriority21 Intense Emphasis;\lsdqformat1\lsdpriority31 Subtle Reference;\lsdqformat1\lsdpriority32 Intense Reference;\lsdqformat1\lsdpriority33 Book Title;\lsdsemihidden1\lsdunhideused1\lsdpriority37 Bibliography;
|
||||||
|
\lsdsemihidden1\lsdunhideused1\lsdqformat1\lsdpriority39 TOC Heading;}}}
|
||||||
16
cucumber/features/environment.py
Normal file
16
cucumber/features/environment.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
def before_all(context):
|
||||||
|
context.endpoint = None
|
||||||
|
context.request_data = None
|
||||||
|
context.files = {}
|
||||||
|
context.response = None
|
||||||
|
|
||||||
|
def after_scenario(context, scenario):
|
||||||
|
if hasattr(context, 'files'):
|
||||||
|
for file in context.files.values():
|
||||||
|
file.close()
|
||||||
|
if os.path.exists('response_file'):
|
||||||
|
os.remove('response_file')
|
||||||
|
if hasattr(context, 'file_name') and os.path.exists(context.file_name):
|
||||||
|
os.remove(context.file_name)
|
||||||
130
cucumber/features/examples.feature
Normal file
130
cucumber/features/examples.feature
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
@example
|
||||||
|
Feature: API Validation
|
||||||
|
|
||||||
|
@positive @password
|
||||||
|
Scenario: Remove password
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 3 pages
|
||||||
|
And the pdf is encrypted with password "password123"
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| password | password123 |
|
||||||
|
When I send the API request to the endpoint "/api/v1/security/remove-password"
|
||||||
|
Then the response content type should be "application/pdf"
|
||||||
|
And the response file should have size greater than 0
|
||||||
|
And the response PDF is not passworded
|
||||||
|
And the response status code should be 200
|
||||||
|
|
||||||
|
@negative @password
|
||||||
|
Scenario: Remove password wrong password
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 3 pages
|
||||||
|
And the pdf is encrypted with password "password123"
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| password | wrongPassword |
|
||||||
|
When I send the API request to the endpoint "/api/v1/security/remove-password"
|
||||||
|
Then the response status code should be 500
|
||||||
|
And the response should contain error message "Internal Server Error"
|
||||||
|
|
||||||
|
@positive @info
|
||||||
|
Scenario: Get info
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
When I send the API request to the endpoint "/api/v1/security/get-info-on-pdf"
|
||||||
|
Then the response content type should be "application/json"
|
||||||
|
And the response file should have size greater than 100
|
||||||
|
And the response status code should be 200
|
||||||
|
|
||||||
|
@positive @password
|
||||||
|
Scenario: Add password
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 3 pages
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| password | password123 |
|
||||||
|
When I send the API request to the endpoint "/api/v1/security/add-password"
|
||||||
|
Then the response content type should be "application/pdf"
|
||||||
|
And the response file should have size greater than 100
|
||||||
|
And the response PDF is passworded
|
||||||
|
And the response status code should be 200
|
||||||
|
|
||||||
|
@positive @password
|
||||||
|
Scenario: Add password with other params
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 3 pages
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| ownerPassword | ownerPass |
|
||||||
|
| password | password123 |
|
||||||
|
| keyLength | 256 |
|
||||||
|
| canPrint | true |
|
||||||
|
| canModify | false |
|
||||||
|
When I send the API request to the endpoint "/api/v1/security/add-password"
|
||||||
|
Then the response content type should be "application/pdf"
|
||||||
|
And the response file should have size greater than 100
|
||||||
|
And the response PDF is passworded
|
||||||
|
And the response status code should be 200
|
||||||
|
|
||||||
|
@positive @watermark
|
||||||
|
Scenario: Add watermark
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 3 pages
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| watermarkType | text |
|
||||||
|
| watermarkText | Sample Watermark |
|
||||||
|
| fontSize | 30 |
|
||||||
|
| rotation | 45 |
|
||||||
|
| opacity | 0.5 |
|
||||||
|
| widthSpacer | 50 |
|
||||||
|
| heightSpacer | 50 |
|
||||||
|
When I send the API request to the endpoint "/api/v1/security/add-watermark"
|
||||||
|
Then the response content type should be "application/pdf"
|
||||||
|
And the response file should have size greater than 100
|
||||||
|
And the response status code should be 200
|
||||||
|
|
||||||
|
@positive
|
||||||
|
Scenario: Remove blank pages
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 3 blank pages
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| threshold | 90 |
|
||||||
|
| whitePercent | 99.9 |
|
||||||
|
When I send the API request to the endpoint "/api/v1/misc/remove-blanks"
|
||||||
|
Then the response content type should be "application/pdf"
|
||||||
|
And the response file should have size greater than 0
|
||||||
|
And the response PDF should contain 0 pages
|
||||||
|
And the response status code should be 200
|
||||||
|
|
||||||
|
@positive @flatten
|
||||||
|
Scenario: Flatten PDF
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| flattenOnlyForms | false |
|
||||||
|
When I send the API request to the endpoint "/api/v1/misc/flatten"
|
||||||
|
Then the response content type should be "application/pdf"
|
||||||
|
And the response file should have size greater than 0
|
||||||
|
And the response status code should be 200
|
||||||
|
|
||||||
|
@positive @metadata
|
||||||
|
Scenario: Update metadata
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| author | John Doe |
|
||||||
|
| title | Sample Title |
|
||||||
|
| subject | Sample Subject |
|
||||||
|
| keywords | sample, test |
|
||||||
|
| producer | Test Producer |
|
||||||
|
When I send the API request to the endpoint "/api/v1/misc/update-metadata"
|
||||||
|
Then the response content type should be "application/pdf"
|
||||||
|
And the response file should have size greater than 0
|
||||||
|
And the response PDF metadata should include "Author" as "John Doe"
|
||||||
|
And the response PDF metadata should include "Keywords" as "sample, test"
|
||||||
|
And the response PDF metadata should include "Subject" as "Sample Subject"
|
||||||
|
And the response PDF metadata should include "Title" as "Sample Title"
|
||||||
|
And the response status code should be 200
|
||||||
|
|
||||||
|
|
||||||
228
cucumber/features/external.feature
Normal file
228
cucumber/features/external.feature
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
Feature: API Validation
|
||||||
|
|
||||||
|
|
||||||
|
@libre @positive
|
||||||
|
Scenario: Repair PDF
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
When I send the API request to the endpoint "/api/v1/misc/repair"
|
||||||
|
Then the response content type should be "application/pdf"
|
||||||
|
And the response file should have size greater than 0
|
||||||
|
And the response status code should be 200
|
||||||
|
|
||||||
|
|
||||||
|
@ocr @positive
|
||||||
|
Scenario: Process PDF with OCR
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| languages | eng |
|
||||||
|
| sidecar | false |
|
||||||
|
| deskew | true |
|
||||||
|
| clean | true |
|
||||||
|
| cleanFinal | true |
|
||||||
|
| ocrType | Normal |
|
||||||
|
| ocrRenderType | hocr |
|
||||||
|
| removeImagesAfter| false |
|
||||||
|
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
|
||||||
|
Then the response content type should be "application/pdf"
|
||||||
|
And the response file should have size greater than 0
|
||||||
|
And the response status code should be 200
|
||||||
|
|
||||||
|
|
||||||
|
@ocr @positive
|
||||||
|
Scenario: Extract Image Scans
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 3 images on 2 pages
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| angleThreshold | 5 |
|
||||||
|
| tolerance | 20 |
|
||||||
|
| minArea | 8000 |
|
||||||
|
| minContourArea | 500 |
|
||||||
|
| borderSize | 1 |
|
||||||
|
When I send the API request to the endpoint "/api/v1/misc/extract-image-scans"
|
||||||
|
Then the response content type should be "application/octet-stream"
|
||||||
|
And the response file should have extension ".zip"
|
||||||
|
And the response ZIP should contain 2 files
|
||||||
|
And the response file should have size greater than 0
|
||||||
|
And the response status code should be 200
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ocr @negative
|
||||||
|
Scenario: Process PDF with text and OCR with type normal
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 3 pages with random text
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| languages | eng |
|
||||||
|
| sidecar | false |
|
||||||
|
| deskew | true |
|
||||||
|
| clean | true |
|
||||||
|
| cleanFinal | true |
|
||||||
|
| ocrType | Normal |
|
||||||
|
| ocrRenderType | hocr |
|
||||||
|
| removeImagesAfter| false |
|
||||||
|
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
|
||||||
|
Then the response status code should be 500
|
||||||
|
|
||||||
|
@ocr @positive
|
||||||
|
Scenario: Process PDF with OCR
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| languages | eng |
|
||||||
|
| sidecar | false |
|
||||||
|
| deskew | true |
|
||||||
|
| clean | true |
|
||||||
|
| cleanFinal | true |
|
||||||
|
| ocrType | Force |
|
||||||
|
| ocrRenderType | hocr |
|
||||||
|
| removeImagesAfter| false |
|
||||||
|
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
|
||||||
|
Then the response content type should be "application/pdf"
|
||||||
|
And the response file should have size greater than 0
|
||||||
|
And the response status code should be 200
|
||||||
|
|
||||||
|
@ocr @positive
|
||||||
|
Scenario: Process PDF with OCR with sidecar
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| languages | eng |
|
||||||
|
| sidecar | true |
|
||||||
|
| deskew | true |
|
||||||
|
| clean | true |
|
||||||
|
| cleanFinal | true |
|
||||||
|
| ocrType | Force |
|
||||||
|
| ocrRenderType | hocr |
|
||||||
|
| removeImagesAfter| false |
|
||||||
|
When I send the API request to the endpoint "/api/v1/misc/ocr-pdf"
|
||||||
|
Then the response content type should be "application/octet-stream"
|
||||||
|
And the response file should have extension ".zip"
|
||||||
|
And the response ZIP should contain 2 files
|
||||||
|
And the response file should have size greater than 0
|
||||||
|
And the response status code should be 200
|
||||||
|
|
||||||
|
|
||||||
|
@libre @positive
|
||||||
|
Scenario Outline: Convert PDF to various word formats
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 3 pages with random text
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| outputFormat | <format> |
|
||||||
|
When I send the API request to the endpoint "/api/v1/convert/pdf/word"
|
||||||
|
Then the response status code should be 200
|
||||||
|
And the response file should have size greater than 100
|
||||||
|
And the response file should have extension "<extension>"
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
| format | extension |
|
||||||
|
| docx | .docx |
|
||||||
|
| odt | .odt |
|
||||||
|
| doc | .doc |
|
||||||
|
|
||||||
|
@ocr
|
||||||
|
Scenario: PDFA
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 3 pages with random text
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| outputFormat | pdfa |
|
||||||
|
When I send the API request to the endpoint "/api/v1/convert/pdf/pdfa"
|
||||||
|
Then the response status code should be 200
|
||||||
|
And the response file should have extension ".pdf"
|
||||||
|
And the response file should have size greater than 100
|
||||||
|
|
||||||
|
@ocr
|
||||||
|
Scenario: PDFA1
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 3 pages with random text
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| outputFormat | pdfa-1 |
|
||||||
|
When I send the API request to the endpoint "/api/v1/convert/pdf/pdfa"
|
||||||
|
Then the response status code should be 200
|
||||||
|
And the response file should have extension ".pdf"
|
||||||
|
And the response file should have size greater than 100
|
||||||
|
|
||||||
|
@compress @ghostscript @positive
|
||||||
|
Scenario: Compress
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 3 pages with random text
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| optimizeLevel | 4 |
|
||||||
|
When I send the API request to the endpoint "/api/v1/misc/compress-pdf"
|
||||||
|
Then the response status code should be 200
|
||||||
|
And the response file should have extension ".pdf"
|
||||||
|
And the response file should have size greater than 100
|
||||||
|
|
||||||
|
@compress @ghostscript @positive
|
||||||
|
Scenario: Compress
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 3 pages with random text
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| optimizeLevel | 1 |
|
||||||
|
| expectedOutputSize | 5KB |
|
||||||
|
When I send the API request to the endpoint "/api/v1/misc/compress-pdf"
|
||||||
|
Then the response status code should be 200
|
||||||
|
And the response file should have extension ".pdf"
|
||||||
|
And the response file should have size greater than 100
|
||||||
|
|
||||||
|
|
||||||
|
@compress @ghostscript @positive
|
||||||
|
Scenario: Compress
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 3 pages with random text
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| optimizeLevel | 1 |
|
||||||
|
| expectedOutputSize | 5KB |
|
||||||
|
When I send the API request to the endpoint "/api/v1/misc/compress-pdf"
|
||||||
|
Then the response status code should be 200
|
||||||
|
And the response file should have extension ".pdf"
|
||||||
|
And the response file should have size greater than 100
|
||||||
|
|
||||||
|
@libre @positive
|
||||||
|
Scenario Outline: Convert PDF to various types
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 3 pages with random text
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| outputFormat | <format> |
|
||||||
|
When I send the API request to the endpoint "/api/v1/convert/pdf/<type>"
|
||||||
|
Then the response status code should be 200
|
||||||
|
And the response file should have size greater than 100
|
||||||
|
And the response file should have extension "<extension>"
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
| type | format | extension |
|
||||||
|
| text | rtf | .rtf |
|
||||||
|
| text | txt | .txt |
|
||||||
|
| presentation | ppt | .ppt |
|
||||||
|
| presentation | pptx | .pptx |
|
||||||
|
| presentation | odp | .odp |
|
||||||
|
| html | html | .zip |
|
||||||
|
|
||||||
|
|
||||||
|
@libre @positive @topdf
|
||||||
|
Scenario Outline: Convert PDF to various types
|
||||||
|
Given I use an example file at "exampleFiles/example<extension>" as parameter "fileInput"
|
||||||
|
When I send the API request to the endpoint "/api/v1/convert/file/pdf"
|
||||||
|
Then the response status code should be 200
|
||||||
|
And the response file should have size greater than 100
|
||||||
|
And the response file should have extension ".pdf"
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
| extension |
|
||||||
|
| .docx |
|
||||||
|
| .odp |
|
||||||
|
| .odt |
|
||||||
|
| .pptx |
|
||||||
|
| .rtf |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
96
cucumber/features/general.feature
Normal file
96
cucumber/features/general.feature
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
@general
|
||||||
|
Feature: API Validation
|
||||||
|
|
||||||
|
|
||||||
|
@split-pdf-by-sections @positive
|
||||||
|
Scenario Outline: split-pdf-by-sections with different parameters
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 2 pages
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| horizontalDivisions | <horizontalDivisions> |
|
||||||
|
| verticalDivisions | <verticalDivisions> |
|
||||||
|
| merge | true |
|
||||||
|
When I send the API request to the endpoint "/api/v1/general/split-pdf-by-sections"
|
||||||
|
Then the response content type should be "application/pdf"
|
||||||
|
And the response file should have size greater than 200
|
||||||
|
And the response status code should be 200
|
||||||
|
And the response PDF should contain <page_count> pages
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
| horizontalDivisions | verticalDivisions | page_count |
|
||||||
|
| 0 | 1 | 4 |
|
||||||
|
| 1 | 1 | 8 |
|
||||||
|
| 1 | 2 | 12 |
|
||||||
|
| 2 | 2 | 18 |
|
||||||
|
|
||||||
|
@split-pdf-by-sections @positive
|
||||||
|
Scenario Outline: split-pdf-by-sections with different parameters
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 2 pages
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| horizontalDivisions | <horizontalDivisions> |
|
||||||
|
| verticalDivisions | <verticalDivisions> |
|
||||||
|
| merge | true |
|
||||||
|
When I send the API request to the endpoint "/api/v1/general/split-pdf-by-sections"
|
||||||
|
Then the response content type should be "application/pdf"
|
||||||
|
And the response file should have size greater than 200
|
||||||
|
And the response status code should be 200
|
||||||
|
And the response PDF should contain <page_count> pages
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
| horizontalDivisions | verticalDivisions | page_count |
|
||||||
|
| 0 | 1 | 4 |
|
||||||
|
| 1 | 1 | 8 |
|
||||||
|
| 1 | 2 | 12 |
|
||||||
|
| 2 | 2 | 18 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@split-pdf-by-pages @positive
|
||||||
|
Scenario Outline: split-pdf-by-pages with different parameters
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 20 pages
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| fileInput | fileInput |
|
||||||
|
| pageNumbers | <pageNumbers> |
|
||||||
|
When I send the API request to the endpoint "/api/v1/general/split-pages"
|
||||||
|
Then the response content type should be "application/octet-stream"
|
||||||
|
And the response status code should be 200
|
||||||
|
And the response file should have size greater than 200
|
||||||
|
And the response ZIP should contain <file_count> files
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
| pageNumbers | file_count |
|
||||||
|
| 1,3,5-9 | 8 |
|
||||||
|
| all | 20 |
|
||||||
|
| 2n+1 | 11 |
|
||||||
|
| 3n | 7 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@split-pdf-by-size-or-count @positive
|
||||||
|
Scenario Outline: split-pdf-by-size-or-count with different parameters
|
||||||
|
Given I generate a PDF file as "fileInput"
|
||||||
|
And the pdf contains 20 pages
|
||||||
|
And the request data includes
|
||||||
|
| parameter | value |
|
||||||
|
| fileInput | fileInput |
|
||||||
|
| splitType | <splitType> |
|
||||||
|
| splitValue | <splitValue> |
|
||||||
|
When I send the API request to the endpoint "/api/v1/general/split-by-size-or-count"
|
||||||
|
Then the response content type should be "application/octet-stream"
|
||||||
|
And the response status code should be 200
|
||||||
|
And the response file should have size greater than 200
|
||||||
|
And the response ZIP file should contain <doc_count> documents each having <pages_per_doc> pages
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
| splitType | splitValue | doc_count | pages_per_doc |
|
||||||
|
| 1 | 5 | 4 | 5 |
|
||||||
|
| 2 | 2 | 2 | 10 |
|
||||||
|
| 2 | 4 | 4 | 5 |
|
||||||
|
| 1 | 10 | 2 | 10 |
|
||||||
|
|
||||||
|
|
||||||
307
cucumber/features/steps/step_definitions.py
Normal file
307
cucumber/features/steps/step_definitions.py
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
import os
|
||||||
|
import requests
|
||||||
|
from behave import given, when, then
|
||||||
|
from PyPDF2 import PdfWriter, PdfReader
|
||||||
|
import io
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
from reportlab.lib.pagesizes import letter
|
||||||
|
from reportlab.pdfgen import canvas
|
||||||
|
import mimetypes
|
||||||
|
import requests
|
||||||
|
import zipfile
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
#########
|
||||||
|
# GIVEN #
|
||||||
|
#########
|
||||||
|
|
||||||
|
@given('I generate a PDF file as "{fileInput}"')
|
||||||
|
def step_generate_pdf(context, fileInput):
|
||||||
|
context.param_name = fileInput
|
||||||
|
context.file_name = "genericNonCustomisableName.pdf"
|
||||||
|
writer = PdfWriter()
|
||||||
|
writer.add_blank_page(width=72, height=72) # Single blank page
|
||||||
|
with open(context.file_name, 'wb') as f:
|
||||||
|
writer.write(f)
|
||||||
|
if not hasattr(context, 'files'):
|
||||||
|
context.files = {}
|
||||||
|
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||||
|
|
||||||
|
|
||||||
|
@given('I use an example file at "{filePath}" as parameter "{fileInput}"')
|
||||||
|
def step_use_example_file(context, filePath, fileInput):
|
||||||
|
context.param_name = fileInput
|
||||||
|
context.file_name = filePath.split('/')[-1]
|
||||||
|
if not hasattr(context, 'files'):
|
||||||
|
context.files = {}
|
||||||
|
|
||||||
|
# Ensure the file exists before opening
|
||||||
|
try:
|
||||||
|
example_file = open(filePath, 'rb')
|
||||||
|
context.files[context.param_name] = example_file
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise FileNotFoundError(f"The example file '{filePath}' does not exist.")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@given('the pdf contains {page_count:d} pages')
|
||||||
|
def step_pdf_contains_pages(context, page_count):
|
||||||
|
writer = PdfWriter()
|
||||||
|
for i in range(page_count):
|
||||||
|
writer.add_blank_page(width=72, height=72)
|
||||||
|
with open(context.file_name, 'wb') as f:
|
||||||
|
writer.write(f)
|
||||||
|
context.files[context.param_name].close()
|
||||||
|
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||||
|
|
||||||
|
# Duplicate for now...
|
||||||
|
@given('the pdf contains {page_count:d} blank pages')
|
||||||
|
def step_pdf_contains_blank_pages(context, page_count):
|
||||||
|
writer = PdfWriter()
|
||||||
|
for i in range(page_count):
|
||||||
|
writer.add_blank_page(width=72, height=72)
|
||||||
|
with open(context.file_name, 'wb') as f:
|
||||||
|
writer.write(f)
|
||||||
|
context.files[context.param_name].close()
|
||||||
|
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def create_black_box_image(file_name, size):
|
||||||
|
can = canvas.Canvas(file_name, pagesize=size)
|
||||||
|
width, height = size
|
||||||
|
can.setFillColorRGB(0, 0, 0)
|
||||||
|
can.rect(0, 0, width, height, fill=1)
|
||||||
|
can.showPage()
|
||||||
|
can.save()
|
||||||
|
|
||||||
|
def create_pdf_with_black_boxes(file_name, image_count, page_count):
|
||||||
|
page_width, page_height = letter
|
||||||
|
box_size = 72 # 1 inch by 1 inch black box
|
||||||
|
boxes_per_page = image_count // page_count + (1 if image_count % page_count != 0 else 0)
|
||||||
|
|
||||||
|
writer = PdfWriter()
|
||||||
|
box_counter = 0
|
||||||
|
|
||||||
|
for page in range(page_count):
|
||||||
|
packet = io.BytesIO()
|
||||||
|
can = canvas.Canvas(packet, pagesize=letter)
|
||||||
|
|
||||||
|
for i in range(boxes_per_page):
|
||||||
|
if box_counter >= image_count:
|
||||||
|
break
|
||||||
|
x = (i % (page_width // box_size)) * box_size
|
||||||
|
y = page_height - ((i // (page_width // box_size) + 1) * box_size)
|
||||||
|
can.setFillColorRGB(0, 0, 0)
|
||||||
|
can.rect(x, y, box_size, box_size, fill=1)
|
||||||
|
box_counter += 1
|
||||||
|
|
||||||
|
can.showPage()
|
||||||
|
can.save()
|
||||||
|
packet.seek(0)
|
||||||
|
new_pdf = PdfReader(packet)
|
||||||
|
writer.add_page(new_pdf.pages[0])
|
||||||
|
|
||||||
|
with open(file_name, 'wb') as f:
|
||||||
|
writer.write(f)
|
||||||
|
|
||||||
|
@given('the pdf contains {image_count:d} images on {page_count:d} pages')
|
||||||
|
def step_pdf_contains_images(context, image_count, page_count):
|
||||||
|
if not hasattr(context, 'param_name'):
|
||||||
|
context.param_name = "default"
|
||||||
|
context.file_name = "genericNonCustomisableName.pdf"
|
||||||
|
create_pdf_with_black_boxes(context.file_name, image_count, page_count)
|
||||||
|
if not hasattr(context, 'files'):
|
||||||
|
context.files = {}
|
||||||
|
if context.param_name in context.files:
|
||||||
|
context.files[context.param_name].close()
|
||||||
|
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||||
|
|
||||||
|
|
||||||
|
@given('the pdf contains {page_count:d} pages with random text')
|
||||||
|
def step_pdf_contains_pages_with_random_text(context, page_count):
|
||||||
|
buffer = io.BytesIO()
|
||||||
|
c = canvas.Canvas(buffer, pagesize=letter)
|
||||||
|
width, height = letter
|
||||||
|
|
||||||
|
for _ in range(page_count):
|
||||||
|
text = ''.join(random.choices(string.ascii_letters + string.digits, k=100))
|
||||||
|
c.drawString(100, height - 100, text)
|
||||||
|
c.showPage()
|
||||||
|
|
||||||
|
c.save()
|
||||||
|
|
||||||
|
with open(context.file_name, 'wb') as f:
|
||||||
|
f.write(buffer.getvalue())
|
||||||
|
|
||||||
|
context.files[context.param_name].close()
|
||||||
|
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||||
|
|
||||||
|
@given('the pdf pages all contain the text "{text}"')
|
||||||
|
def step_pdf_pages_contain_text(context, text):
|
||||||
|
buffer = io.BytesIO()
|
||||||
|
c = canvas.Canvas(buffer, pagesize=letter)
|
||||||
|
width, height = letter
|
||||||
|
|
||||||
|
for _ in range(len(PdfReader(context.file_name).pages)):
|
||||||
|
c.drawString(100, height - 100, text)
|
||||||
|
c.showPage()
|
||||||
|
|
||||||
|
c.save()
|
||||||
|
|
||||||
|
with open(context.file_name, 'wb') as f:
|
||||||
|
f.write(buffer.getvalue())
|
||||||
|
|
||||||
|
context.files[context.param_name].close()
|
||||||
|
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||||
|
|
||||||
|
@given('the pdf is encrypted with password "{password}"')
|
||||||
|
def step_encrypt_pdf(context, password):
|
||||||
|
writer = PdfWriter()
|
||||||
|
reader = PdfReader(context.file_name)
|
||||||
|
for i in range(len(reader.pages)):
|
||||||
|
writer.add_page(reader.pages[i])
|
||||||
|
writer.encrypt(password)
|
||||||
|
with open(context.file_name, 'wb') as f:
|
||||||
|
writer.write(f)
|
||||||
|
context.files[context.param_name].close()
|
||||||
|
context.files[context.param_name] = open(context.file_name, 'rb')
|
||||||
|
|
||||||
|
@given('the request data is')
|
||||||
|
def step_request_data(context):
|
||||||
|
context.request_data = eval(context.text)
|
||||||
|
|
||||||
|
@given('the request data includes')
|
||||||
|
def step_request_data_table(context):
|
||||||
|
context.request_data = {row['parameter']: row['value'] for row in context.table}
|
||||||
|
|
||||||
|
@given('save the generated PDF file as "{filename}" for debugging')
|
||||||
|
def save_generated_pdf(context, filename):
|
||||||
|
with open(filename, 'wb') as f:
|
||||||
|
f.write(context.files[context.param_name].read())
|
||||||
|
print(f"Saved generated PDF content to {filename}")
|
||||||
|
|
||||||
|
########
|
||||||
|
# WHEN #
|
||||||
|
########
|
||||||
|
|
||||||
|
@when('I send the API request to the endpoint "{endpoint}"')
|
||||||
|
def step_send_api_request(context, endpoint):
|
||||||
|
url = f"http://localhost:8080{endpoint}"
|
||||||
|
files = context.files if hasattr(context, 'files') else {}
|
||||||
|
|
||||||
|
if not hasattr(context, 'request_data') or context.request_data is None:
|
||||||
|
context.request_data = {}
|
||||||
|
|
||||||
|
form_data = []
|
||||||
|
for key, value in context.request_data.items():
|
||||||
|
form_data.append((key, (None, value)))
|
||||||
|
|
||||||
|
for key, file in files.items():
|
||||||
|
mime_type, _ = mimetypes.guess_type(file.name)
|
||||||
|
mime_type = mime_type or 'application/octet-stream'
|
||||||
|
print(f"form_data {file.name} with {mime_type}")
|
||||||
|
form_data.append((key, (file.name, file, mime_type)))
|
||||||
|
|
||||||
|
response = requests.post(url, files=form_data)
|
||||||
|
context.response = response
|
||||||
|
|
||||||
|
########
|
||||||
|
# THEN #
|
||||||
|
########
|
||||||
|
|
||||||
|
@then('the response content type should be "{content_type}"')
|
||||||
|
def step_check_response_content_type(context, content_type):
|
||||||
|
actual_content_type = context.response.headers.get('Content-Type', '')
|
||||||
|
assert actual_content_type.startswith(content_type), f"Expected {content_type} but got {actual_content_type}. Response content: {context.response.content}"
|
||||||
|
|
||||||
|
@then('the response file should have size greater than {size:d}')
|
||||||
|
def step_check_response_file_size(context, size):
|
||||||
|
response_file = io.BytesIO(context.response.content)
|
||||||
|
assert len(response_file.getvalue()) > size
|
||||||
|
|
||||||
|
@then('the response PDF is not passworded')
|
||||||
|
def step_check_response_pdf_not_passworded(context):
|
||||||
|
response_file = io.BytesIO(context.response.content)
|
||||||
|
reader = PdfReader(response_file)
|
||||||
|
assert not reader.is_encrypted
|
||||||
|
|
||||||
|
@then('the response PDF is passworded')
|
||||||
|
def step_check_response_pdf_passworded(context):
|
||||||
|
response_file = io.BytesIO(context.response.content)
|
||||||
|
try:
|
||||||
|
reader = PdfReader(response_file)
|
||||||
|
assert reader.is_encrypted
|
||||||
|
except PdfReadError as e:
|
||||||
|
raise AssertionError(f"Failed to read PDF: {str(e)}. Response content: {context.response.content}")
|
||||||
|
except Exception as e:
|
||||||
|
raise AssertionError(f"An error occurred: {str(e)}. Response content: {context.response.content}")
|
||||||
|
|
||||||
|
@then('the response status code should be {status_code:d}')
|
||||||
|
def step_check_response_status_code(context, status_code):
|
||||||
|
assert context.response.status_code == status_code, f"Expected status code {status_code} but got {context.response.status_code}"
|
||||||
|
|
||||||
|
@then('the response should contain error message "{message}"')
|
||||||
|
def step_check_response_error_message(context, message):
|
||||||
|
response_json = context.response.json()
|
||||||
|
assert response_json.get('error') == message, f"Expected error message '{message}' but got '{response_json.get('error')}'"
|
||||||
|
|
||||||
|
@then('the response PDF should contain {page_count:d} pages')
|
||||||
|
def step_check_response_pdf_page_count(context, page_count):
|
||||||
|
response_file = io.BytesIO(context.response.content)
|
||||||
|
reader = PdfReader(response_file)
|
||||||
|
assert len(reader.pages) == page_count, f"Expected {page_count} pages but got {len(reader.pages)} pages"
|
||||||
|
|
||||||
|
@then('the response PDF metadata should include "{metadata_key}" as "{metadata_value}"')
|
||||||
|
def step_check_response_pdf_metadata(context, metadata_key, metadata_value):
|
||||||
|
response_file = io.BytesIO(context.response.content)
|
||||||
|
reader = PdfReader(response_file)
|
||||||
|
metadata = reader.metadata
|
||||||
|
assert metadata.get("/" + metadata_key) == metadata_value, f"Expected {metadata_key} to be '{metadata_value}' but got '{metadata.get(metadata_key)}'"
|
||||||
|
|
||||||
|
@then('the response file should have extension "{extension}"')
|
||||||
|
def step_check_response_file_extension(context, extension):
|
||||||
|
content_disposition = context.response.headers.get('Content-Disposition', '')
|
||||||
|
filename = ""
|
||||||
|
if content_disposition:
|
||||||
|
parts = content_disposition.split(';')
|
||||||
|
for part in parts:
|
||||||
|
if part.strip().startswith('filename'):
|
||||||
|
filename = part.split('=')[1].strip().strip('"')
|
||||||
|
break
|
||||||
|
assert filename.endswith(extension), f"Expected file extension {extension} but got {filename}. Response content: {context.response.content}"
|
||||||
|
|
||||||
|
@then('save the response file as "{filename}" for debugging')
|
||||||
|
def step_save_response_file(context, filename):
|
||||||
|
with open(filename, 'wb') as f:
|
||||||
|
f.write(context.response.content)
|
||||||
|
print(f"Saved response content to {filename}")
|
||||||
|
|
||||||
|
|
||||||
|
@then('the response PDF should contain {page_count:d} pages')
|
||||||
|
def step_check_response_pdf_page_count(context, page_count):
|
||||||
|
response_file = io.BytesIO(context.response.content)
|
||||||
|
reader = PdfReader(io.BytesIO(response_file.getvalue()))
|
||||||
|
actual_page_count = len(reader.pages)
|
||||||
|
assert actual_page_count == page_count, f"Expected {page_count} pages but got {actual_page_count} pages"
|
||||||
|
|
||||||
|
@then('the response ZIP should contain {file_count:d} files')
|
||||||
|
def step_check_response_zip_file_count(context, file_count):
|
||||||
|
response_file = io.BytesIO(context.response.content)
|
||||||
|
with zipfile.ZipFile(io.BytesIO(response_file.getvalue())) as zip_file:
|
||||||
|
actual_file_count = len(zip_file.namelist())
|
||||||
|
assert actual_file_count == file_count, f"Expected {file_count} files but got {actual_file_count} files"
|
||||||
|
|
||||||
|
@then('the response ZIP file should contain {doc_count:d} documents each having {pages_per_doc:d} pages')
|
||||||
|
def step_check_response_zip_doc_page_count(context, doc_count, pages_per_doc):
|
||||||
|
response_file = io.BytesIO(context.response.content)
|
||||||
|
with zipfile.ZipFile(io.BytesIO(response_file.getvalue())) as zip_file:
|
||||||
|
actual_doc_count = len(zip_file.namelist())
|
||||||
|
assert actual_doc_count == doc_count, f"Expected {doc_count} documents but got {actual_doc_count} documents"
|
||||||
|
|
||||||
|
for file_name in zip_file.namelist():
|
||||||
|
with zip_file.open(file_name) as pdf_file:
|
||||||
|
reader = PdfReader(pdf_file)
|
||||||
|
actual_pages_per_doc = len(reader.pages)
|
||||||
|
assert actual_pages_per_doc == pages_per_doc, f"Expected {pages_per_doc} pages per document but got {actual_pages_per_doc} pages in document {file_name}"
|
||||||
5
cucumber/requirements.txt
Normal file
5
cucumber/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
behave
|
||||||
|
requests
|
||||||
|
PyPDF2
|
||||||
|
reportlab
|
||||||
|
PyCryptodome
|
||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ ignore = [
|
|||||||
'language.direction',
|
'language.direction',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[cs_CZ]
|
||||||
|
ignore = [
|
||||||
|
'language.direction',
|
||||||
|
]
|
||||||
|
|
||||||
[de_DE]
|
[de_DE]
|
||||||
ignore = [
|
ignore = [
|
||||||
'AddStampRequest.alphabet',
|
'AddStampRequest.alphabet',
|
||||||
|
|||||||
@@ -8,11 +8,7 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContextInitializer;
|
import org.springframework.context.ApplicationContextInitializer;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
@@ -62,7 +58,7 @@ public class ConfigInitializer
|
|||||||
Files.exists(userPath) ? Files.readAllLines(userPath) : new ArrayList<>();
|
Files.exists(userPath) ? Files.readAllLines(userPath) : new ArrayList<>();
|
||||||
|
|
||||||
List<String> resultLines = new ArrayList<>();
|
List<String> resultLines = new ArrayList<>();
|
||||||
|
int position = 0;
|
||||||
for (String templateLine : templateLines) {
|
for (String templateLine : templateLines) {
|
||||||
// Check if the line is a comment
|
// Check if the line is a comment
|
||||||
if (templateLine.trim().startsWith("#")) {
|
if (templateLine.trim().startsWith("#")) {
|
||||||
@@ -70,7 +66,7 @@ public class ConfigInitializer
|
|||||||
if (!entry.isEmpty()) {
|
if (!entry.isEmpty()) {
|
||||||
// Check if this comment has been uncommented in userLines
|
// Check if this comment has been uncommented in userLines
|
||||||
String key = entry.split(":")[0].trim();
|
String key = entry.split(":")[0].trim();
|
||||||
addLine(resultLines, userLines, templateLine, key);
|
addLine(resultLines, userLines, templateLine, key, position);
|
||||||
} else {
|
} else {
|
||||||
resultLines.add(templateLine);
|
resultLines.add(templateLine);
|
||||||
}
|
}
|
||||||
@@ -78,15 +74,16 @@ public class ConfigInitializer
|
|||||||
// Check if the line is a key-value pair
|
// Check if the line is a key-value pair
|
||||||
else if (templateLine.contains(":")) {
|
else if (templateLine.contains(":")) {
|
||||||
String key = templateLine.split(":")[0].trim();
|
String key = templateLine.split(":")[0].trim();
|
||||||
addLine(resultLines, userLines, templateLine, key);
|
addLine(resultLines, userLines, templateLine, key, position);
|
||||||
}
|
}
|
||||||
// Handle empty lines
|
// Handle empty lines
|
||||||
else if (templateLine.trim().length() == 0) {
|
else if (templateLine.trim().length() == 0) {
|
||||||
resultLines.add("");
|
resultLines.add("");
|
||||||
}
|
}
|
||||||
|
position++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the result to the user settings file
|
// Write the result to the user settings file
|
||||||
Files.write(userPath, resultLines);
|
Files.write(userPath, resultLines);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,14 +93,18 @@ public class ConfigInitializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO check parent value instead of just indent lines for duplicate keys (like enabled etc)
|
||||||
|
private static void addLine(
|
||||||
//TODO check parent value instead of just indent lines for duplicate keys (like enabled etc)
|
List<String> resultLines,
|
||||||
private static void addLine(List<String> resultLines, List<String> userLines, String templateLine, String key) {
|
List<String> userLines,
|
||||||
|
String templateLine,
|
||||||
|
String key,
|
||||||
|
int position) {
|
||||||
boolean added = false;
|
boolean added = false;
|
||||||
int templateIndentationLevel = getIndentationLevel(templateLine);
|
int templateIndentationLevel = getIndentationLevel(templateLine);
|
||||||
|
int pos = 0;
|
||||||
for (String settingsLine : userLines) {
|
for (String settingsLine : userLines) {
|
||||||
if (settingsLine.trim().startsWith(key + ":")) {
|
if (settingsLine.trim().startsWith(key + ":") && position == pos) {
|
||||||
int settingsIndentationLevel = getIndentationLevel(settingsLine);
|
int settingsIndentationLevel = getIndentationLevel(settingsLine);
|
||||||
// Check if it is correct settingsLine and has the same parent as templateLine
|
// Check if it is correct settingsLine and has the same parent as templateLine
|
||||||
if (settingsIndentationLevel == templateIndentationLevel) {
|
if (settingsIndentationLevel == templateIndentationLevel) {
|
||||||
@@ -112,6 +113,7 @@ public class ConfigInitializer
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pos++;
|
||||||
}
|
}
|
||||||
if (!added) {
|
if (!added) {
|
||||||
resultLines.add(templateLine);
|
resultLines.add(templateLine);
|
||||||
@@ -122,7 +124,7 @@ public class ConfigInitializer
|
|||||||
int indentationLevel = 0;
|
int indentationLevel = 0;
|
||||||
String trimmedLine = line.trim();
|
String trimmedLine = line.trim();
|
||||||
if (trimmedLine.startsWith("#")) {
|
if (trimmedLine.startsWith("#")) {
|
||||||
line = trimmedLine.substring(1);
|
line = trimmedLine.substring(1);
|
||||||
}
|
}
|
||||||
for (char c : line.toCharArray()) {
|
for (char c : line.toCharArray()) {
|
||||||
if (c == ' ') {
|
if (c == ' ') {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package stirling.software.SPDF.config.security;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
@@ -36,7 +38,11 @@ import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationS
|
|||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.GithubProvider;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.GoogleProvider;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.KeycloakProvider;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||||
|
|
||||||
@@ -47,6 +53,8 @@ public class SecurityConfiguration {
|
|||||||
|
|
||||||
@Autowired private CustomUserDetailsService userDetailsService;
|
@Autowired private CustomUserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder();
|
return new BCryptPasswordEncoder();
|
||||||
@@ -140,6 +148,7 @@ public class SecurityConfiguration {
|
|||||||
|| trimmedUri.startsWith("/images/")
|
|| trimmedUri.startsWith("/images/")
|
||||||
|| trimmedUri.startsWith("/public/")
|
|| trimmedUri.startsWith("/public/")
|
||||||
|| trimmedUri.startsWith("/css/")
|
|| trimmedUri.startsWith("/css/")
|
||||||
|
|| trimmedUri.startsWith("/fonts/")
|
||||||
|| trimmedUri.startsWith("/js/")
|
|| trimmedUri.startsWith("/js/")
|
||||||
|| trimmedUri.startsWith(
|
|| trimmedUri.startsWith(
|
||||||
"/api/v1/info/status");
|
"/api/v1/info/status");
|
||||||
@@ -150,7 +159,8 @@ public class SecurityConfiguration {
|
|||||||
.authenticationProvider(authenticationProvider());
|
.authenticationProvider(authenticationProvider());
|
||||||
|
|
||||||
// Handle OAUTH2 Logins
|
// Handle OAUTH2 Logins
|
||||||
if (applicationProperties.getSecurity().getOAUTH2().getEnabled()) {
|
if (applicationProperties.getSecurity().getOAUTH2() != null
|
||||||
|
&& applicationProperties.getSecurity().getOAUTH2().getEnabled()) {
|
||||||
|
|
||||||
http.oauth2Login(
|
http.oauth2Login(
|
||||||
oauth2 ->
|
oauth2 ->
|
||||||
@@ -181,9 +191,10 @@ public class SecurityConfiguration {
|
|||||||
.logout(
|
.logout(
|
||||||
logout ->
|
logout ->
|
||||||
logout.logoutSuccessHandler(
|
logout.logoutSuccessHandler(
|
||||||
new CustomOAuth2LogoutSuccessHandler(
|
new CustomOAuth2LogoutSuccessHandler(
|
||||||
this.applicationProperties,
|
this.applicationProperties,
|
||||||
sessionRegistry())));
|
sessionRegistry()))
|
||||||
|
.invalidateHttpSession(true));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
http.csrf(csrf -> csrf.disable())
|
http.csrf(csrf -> csrf.disable())
|
||||||
@@ -200,19 +211,127 @@ public class SecurityConfiguration {
|
|||||||
havingValue = "true",
|
havingValue = "true",
|
||||||
matchIfMissing = false)
|
matchIfMissing = false)
|
||||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
public ClientRegistrationRepository clientRegistrationRepository() {
|
||||||
return new InMemoryClientRegistrationRepository(this.oidcClientRegistration());
|
List<ClientRegistration> registrations = new ArrayList<>();
|
||||||
|
|
||||||
|
githubClientRegistration().ifPresent(registrations::add);
|
||||||
|
oidcClientRegistration().ifPresent(registrations::add);
|
||||||
|
googleClientRegistration().ifPresent(registrations::add);
|
||||||
|
keycloakClientRegistration().ifPresent(registrations::add);
|
||||||
|
|
||||||
|
if (registrations.isEmpty()) {
|
||||||
|
logger.error("At least one OAuth2 provider must be configured");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new InMemoryClientRegistrationRepository(registrations);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientRegistration oidcClientRegistration() {
|
private Optional<ClientRegistration> googleClientRegistration() {
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||||
return ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
if (oauth == null || !oauth.getEnabled()) {
|
||||||
.registrationId("oidc")
|
return Optional.empty();
|
||||||
.clientId(oauth.getClientId())
|
}
|
||||||
.clientSecret(oauth.getClientSecret())
|
Client client = oauth.getClient();
|
||||||
.scope(oauth.getScopes())
|
if (client == null) {
|
||||||
.userNameAttributeName(oauth.getUseAsUsername())
|
return Optional.empty();
|
||||||
.clientName("OIDC")
|
}
|
||||||
.build();
|
GoogleProvider google = client.getGoogle();
|
||||||
|
return google != null && google.isSettingsValid()
|
||||||
|
? Optional.of(
|
||||||
|
ClientRegistration.withRegistrationId("google")
|
||||||
|
.clientId(google.getClientId())
|
||||||
|
.clientSecret(google.getClientSecret())
|
||||||
|
.scope(google.getScopes())
|
||||||
|
.authorizationUri(google.getAuthorizationuri())
|
||||||
|
.tokenUri(google.getTokenuri())
|
||||||
|
.userInfoUri(google.getUserinfouri())
|
||||||
|
.userNameAttributeName(google.getUseAsUsername())
|
||||||
|
.clientName("Google")
|
||||||
|
.redirectUri("{baseUrl}/login/oauth2/code/google")
|
||||||
|
.authorizationGrantType(
|
||||||
|
org.springframework.security.oauth2.core
|
||||||
|
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
|
.build())
|
||||||
|
: Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ClientRegistration> keycloakClientRegistration() {
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||||
|
if (oauth == null || !oauth.getEnabled()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
Client client = oauth.getClient();
|
||||||
|
if (client == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
KeycloakProvider keycloak = client.getKeycloak();
|
||||||
|
|
||||||
|
return keycloak != null && keycloak.isSettingsValid()
|
||||||
|
? Optional.of(
|
||||||
|
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
||||||
|
.registrationId("keycloak")
|
||||||
|
.clientId(keycloak.getClientId())
|
||||||
|
.clientSecret(keycloak.getClientSecret())
|
||||||
|
.scope(keycloak.getScopes())
|
||||||
|
.userNameAttributeName(keycloak.getUseAsUsername())
|
||||||
|
.clientName("Keycloak")
|
||||||
|
.build())
|
||||||
|
: Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ClientRegistration> githubClientRegistration() {
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||||
|
if (oauth == null || !oauth.getEnabled()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
Client client = oauth.getClient();
|
||||||
|
if (client == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
GithubProvider github = client.getGithub();
|
||||||
|
return github != null && github.isSettingsValid()
|
||||||
|
? Optional.of(
|
||||||
|
ClientRegistration.withRegistrationId("github")
|
||||||
|
.clientId(github.getClientId())
|
||||||
|
.clientSecret(github.getClientSecret())
|
||||||
|
.scope(github.getScopes())
|
||||||
|
.authorizationUri(github.getAuthorizationuri())
|
||||||
|
.tokenUri(github.getTokenuri())
|
||||||
|
.userInfoUri(github.getUserinfouri())
|
||||||
|
.userNameAttributeName(github.getUseAsUsername())
|
||||||
|
.clientName("GitHub")
|
||||||
|
.redirectUri("{baseUrl}/login/oauth2/code/github")
|
||||||
|
.authorizationGrantType(
|
||||||
|
org.springframework.security.oauth2.core
|
||||||
|
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
|
.build())
|
||||||
|
: Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ClientRegistration> oidcClientRegistration() {
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||||
|
if (oauth == null
|
||||||
|
|| oauth.getIssuer() == null
|
||||||
|
|| oauth.getIssuer().isEmpty()
|
||||||
|
|| oauth.getClientId() == null
|
||||||
|
|| oauth.getClientId().isEmpty()
|
||||||
|
|| oauth.getClientSecret() == null
|
||||||
|
|| oauth.getClientSecret().isEmpty()
|
||||||
|
|| oauth.getScopes() == null
|
||||||
|
|| oauth.getScopes().isEmpty()
|
||||||
|
|| oauth.getUseAsUsername() == null
|
||||||
|
|| oauth.getUseAsUsername().isEmpty()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(
|
||||||
|
ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
||||||
|
.registrationId("oidc")
|
||||||
|
.clientId(oauth.getClientId())
|
||||||
|
.clientSecret(oauth.getClientSecret())
|
||||||
|
.scope(oauth.getScopes())
|
||||||
|
.userNameAttributeName(oauth.getUseAsUsername())
|
||||||
|
.clientName("OIDC")
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
contextPath + "/images/",
|
contextPath + "/images/",
|
||||||
contextPath + "/public/",
|
contextPath + "/public/",
|
||||||
contextPath + "/css/",
|
contextPath + "/css/",
|
||||||
|
contextPath + "/fonts/",
|
||||||
contextPath + "/js/",
|
contextPath + "/js/",
|
||||||
contextPath + "/pdfjs/",
|
contextPath + "/pdfjs/",
|
||||||
contextPath + "/api/v1/info/status",
|
contextPath + "/api/v1/info/status",
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public class CustomOAuth2AuthenticationFailureHandler
|
|||||||
} else if (exception instanceof LockedException) {
|
} else if (exception instanceof LockedException) {
|
||||||
logger.error("Account locked: ", exception);
|
logger.error("Account locked: ", exception);
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
|
getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
logger.error("Unhandled authentication exception", exception);
|
logger.error("Unhandled authentication exception", exception);
|
||||||
super.onAuthenticationFailure(request, response, exception);
|
super.onAuthenticationFailure(request, response, exception);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.session.SessionRegistry;
|
import org.springframework.security.core.session.SessionRegistry;
|
||||||
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||||
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||||
|
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
@@ -14,6 +15,8 @@ import jakarta.servlet.http.HttpServletResponse;
|
|||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
|
import stirling.software.SPDF.model.Provider;
|
||||||
|
import stirling.software.SPDF.utils.UrlUtils;
|
||||||
|
|
||||||
public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||||
|
|
||||||
@@ -33,11 +36,30 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
|
|||||||
public void onLogoutSuccess(
|
public void onLogoutSuccess(
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
|
|
||||||
String param = "logout=true";
|
String param = "logout=true";
|
||||||
|
String registrationId = null;
|
||||||
|
String issuer = null;
|
||||||
|
String clientId = null;
|
||||||
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||||
String provider = oauth.getProvider() != null ? oauth.getProvider() : "";
|
|
||||||
|
if (authentication instanceof OAuth2AuthenticationToken) {
|
||||||
|
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
|
||||||
|
registrationId = oauthToken.getAuthorizedClientRegistrationId();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Provider provider = oauth.getClient().get(registrationId);
|
||||||
|
issuer = provider.getIssuer();
|
||||||
|
clientId = provider.getClientId();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
registrationId = oauth.getProvider() != null ? oauth.getProvider() : "";
|
||||||
|
issuer = oauth.getIssuer();
|
||||||
|
clientId = oauth.getClientId();
|
||||||
|
}
|
||||||
|
|
||||||
if (request.getParameter("oauth2AuthenticationErrorWeb") != null) {
|
if (request.getParameter("oauth2AuthenticationErrorWeb") != null) {
|
||||||
param = "erroroauth=oauth2AuthenticationErrorWeb";
|
param = "erroroauth=oauth2AuthenticationErrorWeb";
|
||||||
@@ -49,36 +71,46 @@ public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHand
|
|||||||
param = "error=oauth2AutoCreateDisabled";
|
param = "error=oauth2AutoCreateDisabled";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String redirect_url = UrlUtils.getOrigin(request) + "/login?" + param;
|
||||||
|
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = request.getSession(false);
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
String sessionId = session.getId();
|
String sessionId = session.getId();
|
||||||
sessionRegistry.removeSessionInformation(sessionId);
|
sessionRegistry.removeSessionInformation(sessionId);
|
||||||
session.invalidate();
|
session.invalidate();
|
||||||
logger.debug("Session invalidated: " + sessionId);
|
logger.info("Session invalidated: " + sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (provider) {
|
switch (registrationId) {
|
||||||
case "keycloak":
|
case "keycloak":
|
||||||
|
// Add Keycloak specific logout URL if needed
|
||||||
String logoutUrl =
|
String logoutUrl =
|
||||||
oauth.getIssuer()
|
issuer
|
||||||
+ "/protocol/openid-connect/logout"
|
+ "/protocol/openid-connect/logout"
|
||||||
+ "?client_id="
|
+ "?client_id="
|
||||||
+ oauth.getClientId()
|
+ clientId
|
||||||
+ "&post_logout_redirect_uri="
|
+ "&post_logout_redirect_uri="
|
||||||
+ response.encodeRedirectURL(
|
+ response.encodeRedirectURL(redirect_url);
|
||||||
request.getScheme()
|
logger.info("Redirecting to Keycloak logout URL: " + logoutUrl);
|
||||||
+ "://"
|
|
||||||
+ request.getHeader("host")
|
|
||||||
+ "/login?"
|
|
||||||
+ param);
|
|
||||||
logger.debug("Redirecting to Keycloak logout URL: " + logoutUrl);
|
|
||||||
response.sendRedirect(logoutUrl);
|
response.sendRedirect(logoutUrl);
|
||||||
break;
|
break;
|
||||||
|
case "github":
|
||||||
|
// Add GitHub specific logout URL if needed
|
||||||
|
String githubLogoutUrl = "https://github.com/logout";
|
||||||
|
logger.info("Redirecting to GitHub logout URL: " + githubLogoutUrl);
|
||||||
|
response.sendRedirect(githubLogoutUrl);
|
||||||
|
break;
|
||||||
case "google":
|
case "google":
|
||||||
// Add Google specific logout URL if needed
|
// Add Google specific logout URL if needed
|
||||||
|
// String googleLogoutUrl =
|
||||||
|
// "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue="
|
||||||
|
// + response.encodeRedirectURL(redirect_url);
|
||||||
|
// logger.info("Redirecting to Google logout URL: " + googleLogoutUrl);
|
||||||
|
// response.sendRedirect(googleLogoutUrl);
|
||||||
|
// break;
|
||||||
default:
|
default:
|
||||||
String redirectUrl = request.getContextPath() + "/login?" + param;
|
String redirectUrl = request.getContextPath() + "/login?" + param;
|
||||||
logger.debug("Redirecting to default logout URL: " + redirectUrl);
|
logger.info("Redirecting to default logout URL: " + redirectUrl);
|
||||||
response.sendRedirect(redirectUrl);
|
response.sendRedirect(redirectUrl);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
package stirling.software.SPDF.config.security.oauth2;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
|
||||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
|
|
||||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
|
||||||
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
|
||||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
|
||||||
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
|
||||||
|
|
||||||
public class CustomOAuthUserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CustomOAuthUserService.class);
|
|
||||||
|
|
||||||
private final OidcUserService delegate = new OidcUserService();
|
|
||||||
|
|
||||||
private ApplicationProperties applicationProperties;
|
|
||||||
|
|
||||||
public CustomOAuthUserService(ApplicationProperties applicationProperties) {
|
|
||||||
this.applicationProperties = applicationProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
|
|
||||||
String usernameAttribute =
|
|
||||||
applicationProperties.getSecurity().getOAUTH2().getUseAsUsername();
|
|
||||||
try {
|
|
||||||
|
|
||||||
OidcUser user = delegate.loadUser(userRequest);
|
|
||||||
Map<String, Object> attributes = new HashMap<>(user.getAttributes());
|
|
||||||
|
|
||||||
// Ensure the preferred username attribute is present
|
|
||||||
if (!attributes.containsKey(usernameAttribute)) {
|
|
||||||
attributes.put(usernameAttribute, attributes.getOrDefault("email", ""));
|
|
||||||
usernameAttribute = "email";
|
|
||||||
logger.info("Adjusted username attribute to use email");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a new OidcUser with adjusted attributes
|
|
||||||
return new DefaultOidcUser(
|
|
||||||
user.getAuthorities(),
|
|
||||||
userRequest.getIdToken(),
|
|
||||||
user.getUserInfo(),
|
|
||||||
usernameAttribute);
|
|
||||||
} catch (java.lang.IllegalArgumentException e) {
|
|
||||||
throw new OAuth2AuthenticationException(
|
|
||||||
new OAuth2Error(e.getMessage()), e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -135,7 +135,9 @@ public class MergeController {
|
|||||||
throw ex;
|
throw ex;
|
||||||
} finally {
|
} finally {
|
||||||
for (File file : filesToDelete) {
|
for (File file : filesToDelete) {
|
||||||
file.delete();
|
if (file != null) {
|
||||||
|
Files.deleteIfExists(file.toPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,12 +87,12 @@ public class PdfOverlayController {
|
|||||||
} finally {
|
} finally {
|
||||||
for (File overlayPdfFile : overlayPdfFiles) {
|
for (File overlayPdfFile : overlayPdfFiles) {
|
||||||
if (overlayPdfFile != null) {
|
if (overlayPdfFile != null) {
|
||||||
overlayPdfFile.delete();
|
Files.deleteIfExists(overlayPdfFile.toPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (File tempFile : tempFiles) { // Delete temporary files
|
for (File tempFile : tempFiles) { // Delete temporary files
|
||||||
if (tempFile != null) {
|
if (tempFile != null) {
|
||||||
tempFile.delete();
|
Files.deleteIfExists(tempFile.toPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ public class SplitPDFController {
|
|||||||
|
|
||||||
logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
|
logger.info("Successfully created zip file with split documents: {}", zipFile.toString());
|
||||||
byte[] data = Files.readAllBytes(zipFile);
|
byte[] data = Files.readAllBytes(zipFile);
|
||||||
Files.delete(zipFile);
|
Files.deleteIfExists(zipFile);
|
||||||
|
|
||||||
// return the Resource in the response
|
// return the Resource in the response
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
|
|||||||
@@ -63,10 +63,7 @@ public class SplitPdfBySectionsController {
|
|||||||
MergeController mergeController = new MergeController();
|
MergeController mergeController = new MergeController();
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
mergeController.mergeDocuments(splitDocuments).save(baos);
|
mergeController.mergeDocuments(splitDocuments).save(baos);
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), filename + "_split.pdf");
|
||||||
baos.toByteArray(),
|
|
||||||
filename + "_split.pdf",
|
|
||||||
MediaType.APPLICATION_OCTET_STREAM);
|
|
||||||
}
|
}
|
||||||
for (PDDocument doc : splitDocuments) {
|
for (PDDocument doc : splitDocuments) {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
@@ -98,7 +95,7 @@ public class SplitPdfBySectionsController {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
data = Files.readAllBytes(zipFile);
|
data = Files.readAllBytes(zipFile);
|
||||||
Files.delete(zipFile);
|
Files.deleteIfExists(zipFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
|
|||||||
@@ -66,9 +66,8 @@ public class ConvertImgPDFController {
|
|||||||
Integer.valueOf(dpi),
|
Integer.valueOf(dpi),
|
||||||
filename);
|
filename);
|
||||||
|
|
||||||
|
if (result == null || result.length == 0) {
|
||||||
if(result == null || result.length == 0) {
|
logger.error("resultant bytes for {} is null, error converting ", filename);
|
||||||
logger.error("resultant bytes for {} is null, error converting ", filename);
|
|
||||||
}
|
}
|
||||||
if (singleImage) {
|
if (singleImage) {
|
||||||
String docName = filename + "." + imageFormat;
|
String docName = filename + "." + imageFormat;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package stirling.software.SPDF.controller.api.converters;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -41,34 +40,35 @@ public class ConvertOfficeController {
|
|||||||
// Save the uploaded file to a temporary location
|
// Save the uploaded file to a temporary location
|
||||||
Path tempInputFile =
|
Path tempInputFile =
|
||||||
Files.createTempFile("input_", "." + FilenameUtils.getExtension(originalFilename));
|
Files.createTempFile("input_", "." + FilenameUtils.getExtension(originalFilename));
|
||||||
Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
|
inputFile.transferTo(tempInputFile);
|
||||||
|
|
||||||
// Prepare the output file path
|
// Prepare the output file path
|
||||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||||
|
|
||||||
// Run the LibreOffice command
|
try {
|
||||||
List<String> command =
|
// Run the LibreOffice command
|
||||||
new ArrayList<>(
|
List<String> command =
|
||||||
Arrays.asList(
|
new ArrayList<>(
|
||||||
"unoconv",
|
Arrays.asList(
|
||||||
"-vvv",
|
"unoconv",
|
||||||
"-f",
|
"-vvv",
|
||||||
"pdf",
|
"-f",
|
||||||
"-o",
|
"pdf",
|
||||||
tempOutputFile.toString(),
|
"-o",
|
||||||
tempInputFile.toString()));
|
tempOutputFile.toString(),
|
||||||
ProcessExecutorResult returnCode =
|
tempInputFile.toString()));
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
|
ProcessExecutorResult returnCode =
|
||||||
.runCommandWithOutputHandling(command);
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)
|
||||||
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
// Read the converted PDF file
|
// Read the converted PDF file
|
||||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
|
return pdfBytes;
|
||||||
// Clean up the temporary files
|
} finally {
|
||||||
Files.delete(tempInputFile);
|
// Clean up the temporary files
|
||||||
Files.delete(tempOutputFile);
|
if (tempInputFile != null) Files.deleteIfExists(tempInputFile);
|
||||||
|
Files.deleteIfExists(tempOutputFile);
|
||||||
return pdfBytes;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidFileExtension(String fileExtension) {
|
private boolean isValidFileExtension(String fileExtension) {
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ public class ConvertPDFToPDFA {
|
|||||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
|
|
||||||
// Clean up the temporary files
|
// Clean up the temporary files
|
||||||
Files.delete(tempInputFile);
|
Files.deleteIfExists(tempInputFile);
|
||||||
Files.delete(tempOutputFile);
|
Files.deleteIfExists(tempOutputFile);
|
||||||
|
|
||||||
// Return the optimized PDF as a response
|
// Return the optimized PDF as a response
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ public class ConvertWebsiteToPDF {
|
|||||||
pdfBytes = Files.readAllBytes(tempOutputFile);
|
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up the temporary files
|
// Clean up the temporary files
|
||||||
Files.delete(tempOutputFile);
|
Files.deleteIfExists(tempOutputFile);
|
||||||
}
|
}
|
||||||
// Convert URL to a safe filename
|
// Convert URL to a safe filename
|
||||||
String outputFilename = convertURLToFileName(URL);
|
String outputFilename = convertURLToFileName(URL);
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ public class AutoSplitPdfController {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
data = Files.readAllBytes(zipFile);
|
data = Files.readAllBytes(zipFile);
|
||||||
Files.delete(zipFile);
|
Files.deleteIfExists(zipFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ public class BlankPageController {
|
|||||||
String pageText = textStripper.getText(document);
|
String pageText = textStripper.getText(document);
|
||||||
boolean hasText = !pageText.trim().isEmpty();
|
boolean hasText = !pageText.trim().isEmpty();
|
||||||
|
|
||||||
Boolean blank = false;
|
Boolean blank = true;
|
||||||
if (hasText) {
|
if (hasText) {
|
||||||
logger.info("page " + pageIndex + " has text, not blank");
|
logger.info("page " + pageIndex + " has text, not blank");
|
||||||
blank = false;
|
blank = false;
|
||||||
|
|||||||
@@ -136,10 +136,10 @@ public class CompressController {
|
|||||||
// Increase optimization level for next iteration
|
// Increase optimization level for next iteration
|
||||||
optimizeLevel++;
|
optimizeLevel++;
|
||||||
if (autoMode && optimizeLevel > 4) {
|
if (autoMode && optimizeLevel > 4) {
|
||||||
System.out.println("Skipping level 5 due to bad results in auto mode");
|
logger.info("Skipping level 5 due to bad results in auto mode");
|
||||||
sizeMet = true;
|
sizeMet = true;
|
||||||
} else {
|
} else {
|
||||||
System.out.println(
|
logger.info(
|
||||||
"Increasing ghostscript optimisation level to " + optimizeLevel);
|
"Increasing ghostscript optimisation level to " + optimizeLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,10 +230,10 @@ public class CompressController {
|
|||||||
if (currentSize > expectedOutputSize) {
|
if (currentSize > expectedOutputSize) {
|
||||||
// Log the current file size and scaleFactor
|
// Log the current file size and scaleFactor
|
||||||
|
|
||||||
System.out.println(
|
logger.info(
|
||||||
"Current file size: "
|
"Current file size: "
|
||||||
+ FileUtils.byteCountToDisplaySize(currentSize));
|
+ FileUtils.byteCountToDisplaySize(currentSize));
|
||||||
System.out.println("Current scale factor: " + scaleFactor);
|
logger.info("Current scale factor: " + scaleFactor);
|
||||||
|
|
||||||
// The file is still too large, reduce scaleFactor and try again
|
// The file is still too large, reduce scaleFactor and try again
|
||||||
scaleFactor *= 0.9f; // reduce scaleFactor by 10%
|
scaleFactor *= 0.9f; // reduce scaleFactor by 10%
|
||||||
@@ -256,7 +256,6 @@ public class CompressController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the optimized PDF file
|
// Read the optimized PDF file
|
||||||
pdfBytes = Files.readAllBytes(tempOutputFile);
|
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
|
|
||||||
@@ -269,17 +268,18 @@ public class CompressController {
|
|||||||
// Read the original file again
|
// Read the original file again
|
||||||
pdfBytes = Files.readAllBytes(tempInputFile);
|
pdfBytes = Files.readAllBytes(tempInputFile);
|
||||||
}
|
}
|
||||||
|
// Return the optimized PDF as a response
|
||||||
|
String outputFilename =
|
||||||
|
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||||
|
.replaceFirst("[.][^.]+$", "")
|
||||||
|
+ "_Optimized.pdf";
|
||||||
|
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up the temporary files
|
// Clean up the temporary files
|
||||||
Files.delete(tempInputFile);
|
// deleted by multipart file handler deu to transferTo?
|
||||||
Files.delete(tempOutputFile);
|
// Files.deleteIfExists(tempInputFile);
|
||||||
|
Files.deleteIfExists(tempOutputFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the optimized PDF as a response
|
|
||||||
String outputFilename =
|
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "")
|
|
||||||
+ "_Optimized.pdf";
|
|
||||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -103,10 +102,7 @@ public class ExtractImageScansController {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tempInputFile = Files.createTempFile("input_", "." + extension);
|
tempInputFile = Files.createTempFile("input_", "." + extension);
|
||||||
Files.copy(
|
form.getFileInput().transferTo(tempInputFile);
|
||||||
form.getFileInput().getInputStream(),
|
|
||||||
tempInputFile,
|
|
||||||
StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
// Add input file path to images list
|
// Add input file path to images list
|
||||||
images.add(tempInputFile.toString());
|
images.add(tempInputFile.toString());
|
||||||
}
|
}
|
||||||
@@ -176,11 +172,15 @@ public class ExtractImageScansController {
|
|||||||
byte[] zipBytes = Files.readAllBytes(tempZipFile);
|
byte[] zipBytes = Files.readAllBytes(tempZipFile);
|
||||||
|
|
||||||
// Clean up the temporary zip file
|
// Clean up the temporary zip file
|
||||||
Files.delete(tempZipFile);
|
Files.deleteIfExists(tempZipFile);
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
}
|
||||||
|
if (processedImageBytes.size() == 0) {
|
||||||
|
throw new IllegalArgumentException("No images detected");
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Return the processed image as a response
|
// Return the processed image as a response
|
||||||
byte[] imageBytes = processedImageBytes.get(0);
|
byte[] imageBytes = processedImageBytes.get(0);
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
@@ -201,7 +201,7 @@ public class ExtractImageScansController {
|
|||||||
|
|
||||||
if (tempZipFile != null && Files.exists(tempZipFile)) {
|
if (tempZipFile != null && Files.exists(tempZipFile)) {
|
||||||
try {
|
try {
|
||||||
Files.delete(tempZipFile);
|
Files.deleteIfExists(tempZipFile);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Failed to delete temporary zip file: " + tempZipFile, e);
|
logger.error("Failed to delete temporary zip file: " + tempZipFile, e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -91,139 +90,145 @@ public class OCRController {
|
|||||||
}
|
}
|
||||||
// Save the uploaded file to a temporary location
|
// Save the uploaded file to a temporary location
|
||||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||||
Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
|
|
||||||
// Prepare the output file path
|
|
||||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||||
|
|
||||||
// Prepare the output file path
|
|
||||||
Path sidecarTextPath = null;
|
Path sidecarTextPath = null;
|
||||||
|
|
||||||
// Run OCR Command
|
try {
|
||||||
String languageOption = String.join("+", selectedLanguages);
|
inputFile.transferTo(tempInputFile.toFile());
|
||||||
|
|
||||||
List<String> command =
|
// Run OCR Command
|
||||||
new ArrayList<>(
|
String languageOption = String.join("+", selectedLanguages);
|
||||||
Arrays.asList(
|
|
||||||
"ocrmypdf",
|
|
||||||
"--verbose",
|
|
||||||
"2",
|
|
||||||
"--output-type",
|
|
||||||
"pdf",
|
|
||||||
"--pdf-renderer",
|
|
||||||
ocrRenderType));
|
|
||||||
|
|
||||||
if (sidecar != null && sidecar) {
|
List<String> command =
|
||||||
sidecarTextPath = Files.createTempFile("sidecar", ".txt");
|
new ArrayList<>(
|
||||||
command.add("--sidecar");
|
Arrays.asList(
|
||||||
command.add(sidecarTextPath.toString());
|
"ocrmypdf",
|
||||||
}
|
"--verbose",
|
||||||
|
"2",
|
||||||
if (deskew != null && deskew) {
|
"--output-type",
|
||||||
command.add("--deskew");
|
"pdf",
|
||||||
}
|
"--pdf-renderer",
|
||||||
if (clean != null && clean) {
|
ocrRenderType));
|
||||||
command.add("--clean");
|
|
||||||
}
|
|
||||||
if (cleanFinal != null && cleanFinal) {
|
|
||||||
command.add("--clean-final");
|
|
||||||
}
|
|
||||||
if (ocrType != null && !"".equals(ocrType)) {
|
|
||||||
if ("skip-text".equals(ocrType)) {
|
|
||||||
command.add("--skip-text");
|
|
||||||
} else if ("force-ocr".equals(ocrType)) {
|
|
||||||
command.add("--force-ocr");
|
|
||||||
} else if ("Normal".equals(ocrType)) {
|
|
||||||
|
|
||||||
|
if (sidecar != null && sidecar) {
|
||||||
|
sidecarTextPath = Files.createTempFile("sidecar", ".txt");
|
||||||
|
command.add("--sidecar");
|
||||||
|
command.add(sidecarTextPath.toString());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
command.addAll(
|
if (deskew != null && deskew) {
|
||||||
Arrays.asList(
|
command.add("--deskew");
|
||||||
"--language",
|
}
|
||||||
languageOption,
|
if (clean != null && clean) {
|
||||||
tempInputFile.toString(),
|
command.add("--clean");
|
||||||
tempOutputFile.toString()));
|
}
|
||||||
|
if (cleanFinal != null && cleanFinal) {
|
||||||
|
command.add("--clean-final");
|
||||||
|
}
|
||||||
|
if (ocrType != null && !"".equals(ocrType)) {
|
||||||
|
if ("skip-text".equals(ocrType)) {
|
||||||
|
command.add("--skip-text");
|
||||||
|
} else if ("force-ocr".equals(ocrType)) {
|
||||||
|
command.add("--force-ocr");
|
||||||
|
} else if ("Normal".equals(ocrType)) {
|
||||||
|
|
||||||
// Run CLI command
|
}
|
||||||
ProcessExecutorResult result =
|
}
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF)
|
|
||||||
.runCommandWithOutputHandling(command);
|
command.addAll(
|
||||||
if (result.getRc() != 0
|
Arrays.asList(
|
||||||
&& result.getMessages().contains("multiprocessing/synchronize.py")
|
"--language",
|
||||||
&& result.getMessages().contains("OSError: [Errno 38] Function not implemented")) {
|
languageOption,
|
||||||
command.add("--jobs");
|
tempInputFile.toString(),
|
||||||
command.add("1");
|
tempOutputFile.toString()));
|
||||||
result =
|
|
||||||
|
// Run CLI command
|
||||||
|
ProcessExecutorResult result =
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF)
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF)
|
||||||
.runCommandWithOutputHandling(command);
|
.runCommandWithOutputHandling(command);
|
||||||
}
|
if (result.getRc() != 0
|
||||||
|
&& result.getMessages().contains("multiprocessing/synchronize.py")
|
||||||
// Remove images from the OCR processed PDF if the flag is set to true
|
&& result.getMessages()
|
||||||
if (removeImagesAfter != null && removeImagesAfter) {
|
.contains("OSError: [Errno 38] Function not implemented")) {
|
||||||
Path tempPdfWithoutImages = Files.createTempFile("output_", "_no_images.pdf");
|
command.add("--jobs");
|
||||||
|
command.add("1");
|
||||||
List<String> gsCommand =
|
result =
|
||||||
Arrays.asList(
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF)
|
||||||
"gs",
|
.runCommandWithOutputHandling(command);
|
||||||
"-sDEVICE=pdfwrite",
|
|
||||||
"-dFILTERIMAGE",
|
|
||||||
"-o",
|
|
||||||
tempPdfWithoutImages.toString(),
|
|
||||||
tempOutputFile.toString());
|
|
||||||
|
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
|
||||||
.runCommandWithOutputHandling(gsCommand);
|
|
||||||
tempOutputFile = tempPdfWithoutImages;
|
|
||||||
}
|
|
||||||
// Read the OCR processed PDF file
|
|
||||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
|
||||||
// Clean up the temporary files
|
|
||||||
Files.delete(tempInputFile);
|
|
||||||
|
|
||||||
// Return the OCR processed PDF as a response
|
|
||||||
String outputFilename =
|
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "")
|
|
||||||
+ "_OCR.pdf";
|
|
||||||
|
|
||||||
if (sidecar != null && sidecar) {
|
|
||||||
// Create a zip file containing both the PDF and the text file
|
|
||||||
String outputZipFilename =
|
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "")
|
|
||||||
+ "_OCR.zip";
|
|
||||||
Path tempZipFile = Files.createTempFile("output_", ".zip");
|
|
||||||
|
|
||||||
try (ZipOutputStream zipOut =
|
|
||||||
new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) {
|
|
||||||
// Add PDF file to the zip
|
|
||||||
ZipEntry pdfEntry = new ZipEntry(outputFilename);
|
|
||||||
zipOut.putNextEntry(pdfEntry);
|
|
||||||
Files.copy(tempOutputFile, zipOut);
|
|
||||||
zipOut.closeEntry();
|
|
||||||
|
|
||||||
// Add text file to the zip
|
|
||||||
ZipEntry txtEntry = new ZipEntry(outputFilename.replace(".pdf", ".txt"));
|
|
||||||
zipOut.putNextEntry(txtEntry);
|
|
||||||
Files.copy(sidecarTextPath, zipOut);
|
|
||||||
zipOut.closeEntry();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] zipBytes = Files.readAllBytes(tempZipFile);
|
// Remove images from the OCR processed PDF if the flag is set to true
|
||||||
|
if (removeImagesAfter != null && removeImagesAfter) {
|
||||||
|
Path tempPdfWithoutImages = Files.createTempFile("output_", "_no_images.pdf");
|
||||||
|
|
||||||
// Clean up the temporary zip file
|
List<String> gsCommand =
|
||||||
Files.delete(tempZipFile);
|
Arrays.asList(
|
||||||
Files.delete(tempOutputFile);
|
"gs",
|
||||||
Files.delete(sidecarTextPath);
|
"-sDEVICE=pdfwrite",
|
||||||
|
"-dFILTERIMAGE",
|
||||||
|
"-o",
|
||||||
|
tempPdfWithoutImages.toString(),
|
||||||
|
tempOutputFile.toString());
|
||||||
|
|
||||||
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
||||||
|
.runCommandWithOutputHandling(gsCommand);
|
||||||
|
tempOutputFile = tempPdfWithoutImages;
|
||||||
|
}
|
||||||
|
// Read the OCR processed PDF file
|
||||||
|
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
|
|
||||||
// Return the zip file containing both the PDF and the text file
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
|
||||||
zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
|
||||||
} else {
|
|
||||||
// Return the OCR processed PDF as a response
|
// Return the OCR processed PDF as a response
|
||||||
Files.delete(tempOutputFile);
|
String outputFilename =
|
||||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||||
|
.replaceFirst("[.][^.]+$", "")
|
||||||
|
+ "_OCR.pdf";
|
||||||
|
|
||||||
|
if (sidecar != null && sidecar) {
|
||||||
|
// Create a zip file containing both the PDF and the text file
|
||||||
|
String outputZipFilename =
|
||||||
|
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||||
|
.replaceFirst("[.][^.]+$", "")
|
||||||
|
+ "_OCR.zip";
|
||||||
|
Path tempZipFile = Files.createTempFile("output_", ".zip");
|
||||||
|
|
||||||
|
try (ZipOutputStream zipOut =
|
||||||
|
new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) {
|
||||||
|
// Add PDF file to the zip
|
||||||
|
ZipEntry pdfEntry = new ZipEntry(outputFilename);
|
||||||
|
zipOut.putNextEntry(pdfEntry);
|
||||||
|
Files.copy(tempOutputFile, zipOut);
|
||||||
|
zipOut.closeEntry();
|
||||||
|
|
||||||
|
// Add text file to the zip
|
||||||
|
ZipEntry txtEntry = new ZipEntry(outputFilename.replace(".pdf", ".txt"));
|
||||||
|
zipOut.putNextEntry(txtEntry);
|
||||||
|
Files.copy(sidecarTextPath, zipOut);
|
||||||
|
zipOut.closeEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] zipBytes = Files.readAllBytes(tempZipFile);
|
||||||
|
|
||||||
|
// Clean up the temporary zip file
|
||||||
|
Files.deleteIfExists(tempZipFile);
|
||||||
|
Files.deleteIfExists(tempOutputFile);
|
||||||
|
Files.deleteIfExists(sidecarTextPath);
|
||||||
|
|
||||||
|
// Return the zip file containing both the PDF and the text file
|
||||||
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
|
zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||||
|
} else {
|
||||||
|
// Return the OCR processed PDF as a response
|
||||||
|
Files.deleteIfExists(tempOutputFile);
|
||||||
|
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// Clean up the temporary files
|
||||||
|
Files.deleteIfExists(tempOutputFile);
|
||||||
|
// Comment out as transferTo makes multipart handle cleanup
|
||||||
|
// Files.deleteIfExists(tempInputFile);
|
||||||
|
if (sidecarTextPath != null) {
|
||||||
|
Files.deleteIfExists(sidecarTextPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,34 +41,35 @@ public class RepairController {
|
|||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
// Save the uploaded file to a temporary location
|
// Save the uploaded file to a temporary location
|
||||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||||
inputFile.transferTo(tempInputFile.toFile());
|
|
||||||
|
|
||||||
// Prepare the output file path
|
|
||||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||||
|
byte[] pdfBytes = null;
|
||||||
|
inputFile.transferTo(tempInputFile.toFile());
|
||||||
|
try {
|
||||||
|
|
||||||
List<String> command = new ArrayList<>();
|
List<String> command = new ArrayList<>();
|
||||||
command.add("gs");
|
command.add("gs");
|
||||||
command.add("-o");
|
command.add("-o");
|
||||||
command.add(tempOutputFile.toString());
|
command.add(tempOutputFile.toString());
|
||||||
command.add("-sDEVICE=pdfwrite");
|
command.add("-sDEVICE=pdfwrite");
|
||||||
command.add(tempInputFile.toString());
|
command.add(tempInputFile.toString());
|
||||||
|
|
||||||
ProcessExecutorResult returnCode =
|
ProcessExecutorResult returnCode =
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
||||||
.runCommandWithOutputHandling(command);
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
// Read the optimized PDF file
|
// Read the optimized PDF file
|
||||||
byte[] pdfBytes = Files.readAllBytes(tempOutputFile);
|
pdfBytes = Files.readAllBytes(tempOutputFile);
|
||||||
|
|
||||||
// Clean up the temporary files
|
// Return the optimized PDF as a response
|
||||||
Files.delete(tempInputFile);
|
String outputFilename =
|
||||||
Files.delete(tempOutputFile);
|
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||||
|
.replaceFirst("[.][^.]+$", "")
|
||||||
// Return the optimized PDF as a response
|
+ "_repaired.pdf";
|
||||||
String outputFilename =
|
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
} finally {
|
||||||
.replaceFirst("[.][^.]+$", "")
|
// Clean up the temporary files
|
||||||
+ "_repaired.pdf";
|
Files.deleteIfExists(tempInputFile);
|
||||||
return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);
|
Files.deleteIfExists(tempOutputFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,10 +185,12 @@ public class StampController {
|
|||||||
try (InputStream is = classPathResource.getInputStream();
|
try (InputStream is = classPathResource.getInputStream();
|
||||||
FileOutputStream os = new FileOutputStream(tempFile)) {
|
FileOutputStream os = new FileOutputStream(tempFile)) {
|
||||||
IOUtils.copy(is, os);
|
IOUtils.copy(is, os);
|
||||||
|
font = PDType0Font.load(document, tempFile);
|
||||||
|
} finally {
|
||||||
|
if (tempFile != null) {
|
||||||
|
Files.deleteIfExists(tempFile.toPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
font = PDType0Font.load(document, tempFile);
|
|
||||||
tempFile.deleteOnExit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contentStream.setFont(font, fontSize);
|
contentStream.setFont(font, fontSize);
|
||||||
|
|||||||
@@ -150,10 +150,10 @@ public class WatermarkController {
|
|||||||
try (InputStream is = classPathResource.getInputStream();
|
try (InputStream is = classPathResource.getInputStream();
|
||||||
FileOutputStream os = new FileOutputStream(tempFile)) {
|
FileOutputStream os = new FileOutputStream(tempFile)) {
|
||||||
IOUtils.copy(is, os);
|
IOUtils.copy(is, os);
|
||||||
|
font = PDType0Font.load(document, tempFile);
|
||||||
|
} finally {
|
||||||
|
if (tempFile != null) Files.deleteIfExists(tempFile.toPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
font = PDType0Font.load(document, tempFile);
|
|
||||||
tempFile.deleteOnExit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contentStream.setFont(font, fontSize);
|
contentStream.setFont(font, fontSize);
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package stirling.software.SPDF.controller.web;
|
package stirling.software.SPDF.controller.web;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
@@ -21,6 +24,11 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.GithubProvider;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.GoogleProvider;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.KeycloakProvider;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||||
import stirling.software.SPDF.model.Authority;
|
import stirling.software.SPDF.model.Authority;
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
@@ -31,6 +39,7 @@ import stirling.software.SPDF.repository.UserRepository;
|
|||||||
public class AccountWebController {
|
public class AccountWebController {
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(AccountWebController.class);
|
||||||
|
|
||||||
@GetMapping("/login")
|
@GetMapping("/login")
|
||||||
public String login(HttpServletRequest request, Model model, Authentication authentication) {
|
public String login(HttpServletRequest request, Model model, Authentication authentication) {
|
||||||
@@ -38,6 +47,33 @@ public class AccountWebController {
|
|||||||
return "redirect:/";
|
return "redirect:/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, String> providerList = new HashMap<>();
|
||||||
|
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOAUTH2();
|
||||||
|
if (oauth != null) {
|
||||||
|
if (oauth.isSettingsValid()) {
|
||||||
|
providerList.put("oidc", "OpenID Connect");
|
||||||
|
}
|
||||||
|
Client client = oauth.getClient();
|
||||||
|
if (client != null) {
|
||||||
|
GoogleProvider google = client.getGoogle();
|
||||||
|
if (google.isSettingsValid()) {
|
||||||
|
providerList.put("google", "Google");
|
||||||
|
}
|
||||||
|
|
||||||
|
GithubProvider github = client.getGithub();
|
||||||
|
if (github.isSettingsValid()) {
|
||||||
|
providerList.put("github", "Github");
|
||||||
|
}
|
||||||
|
|
||||||
|
KeycloakProvider keycloak = client.getKeycloak();
|
||||||
|
if (keycloak.isSettingsValid()) {
|
||||||
|
providerList.put("keycloak", "Keycloak");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
model.addAttribute("providerlist", providerList);
|
||||||
|
|
||||||
model.addAttribute(
|
model.addAttribute(
|
||||||
"oAuth2Enabled", applicationProperties.getSecurity().getOAUTH2().getEnabled());
|
"oAuth2Enabled", applicationProperties.getSecurity().getOAUTH2().getEnabled());
|
||||||
|
|
||||||
@@ -80,6 +116,21 @@ public class AccountWebController {
|
|||||||
break;
|
break;
|
||||||
case "invalid_token_response":
|
case "invalid_token_response":
|
||||||
erroroauth = "login.oauth2InvalidTokenResponse";
|
erroroauth = "login.oauth2InvalidTokenResponse";
|
||||||
|
break;
|
||||||
|
case "authorization_request_not_found":
|
||||||
|
erroroauth = "login.oauth2RequestNotFound";
|
||||||
|
break;
|
||||||
|
case "access_denied":
|
||||||
|
erroroauth = "login.oauth2AccessDenied";
|
||||||
|
break;
|
||||||
|
case "invalid_user_info_response":
|
||||||
|
erroroauth = "login.oauth2InvalidUserInfoResponse";
|
||||||
|
break;
|
||||||
|
case "invalid_request":
|
||||||
|
erroroauth = "login.oauth2invalidRequest";
|
||||||
|
break;
|
||||||
|
case "invalid_id_token":
|
||||||
|
erroroauth = "login.oauth2InvalidIdToken";
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import java.util.Collection;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.PropertySource;
|
import org.springframework.context.annotation.PropertySource;
|
||||||
@@ -23,6 +25,7 @@ public class ApplicationProperties {
|
|||||||
private Metrics metrics;
|
private Metrics metrics;
|
||||||
private AutomaticallyGenerated automaticallyGenerated;
|
private AutomaticallyGenerated automaticallyGenerated;
|
||||||
private AutoPipeline autoPipeline;
|
private AutoPipeline autoPipeline;
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ApplicationProperties.class);
|
||||||
|
|
||||||
public AutoPipeline getAutoPipeline() {
|
public AutoPipeline getAutoPipeline() {
|
||||||
return autoPipeline != null ? autoPipeline : new AutoPipeline();
|
return autoPipeline != null ? autoPipeline : new AutoPipeline();
|
||||||
@@ -182,13 +185,12 @@ public class ApplicationProperties {
|
|||||||
+ oauth2
|
+ oauth2
|
||||||
+ ", initialLogin="
|
+ ", initialLogin="
|
||||||
+ initialLogin
|
+ initialLogin
|
||||||
+ ", csrfDisabled="
|
+ ", csrfDisabled="
|
||||||
+ csrfDisabled
|
+ csrfDisabled
|
||||||
+ "]";
|
+ "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class InitialLogin {
|
public static class InitialLogin {
|
||||||
|
|
||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@@ -219,22 +221,21 @@ public class ApplicationProperties {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class OAUTH2 {
|
public static class OAUTH2 {
|
||||||
|
private Boolean enabled = false;
|
||||||
private boolean enabled;
|
|
||||||
private String issuer;
|
private String issuer;
|
||||||
private String clientId;
|
private String clientId;
|
||||||
private String clientSecret;
|
private String clientSecret;
|
||||||
private boolean autoCreateUser;
|
private Boolean autoCreateUser = false;
|
||||||
private String useAsUsername;
|
private String useAsUsername;
|
||||||
|
private Collection<String> scopes = new ArrayList<>();
|
||||||
private String provider;
|
private String provider;
|
||||||
|
private Client client = new Client();
|
||||||
|
|
||||||
private Collection<String> scopes = new ArrayList<String>();
|
public Boolean getEnabled() {
|
||||||
|
|
||||||
public boolean getEnabled() {
|
|
||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEnabled(boolean enabled) {
|
public void setEnabled(Boolean enabled) {
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,19 +263,16 @@ public class ApplicationProperties {
|
|||||||
this.clientSecret = clientSecret;
|
this.clientSecret = clientSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getAutoCreateUser() {
|
public Boolean getAutoCreateUser() {
|
||||||
return autoCreateUser;
|
return autoCreateUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAutoCreateUser(boolean autoCreateUser) {
|
public void setAutoCreateUser(Boolean autoCreateUser) {
|
||||||
this.autoCreateUser = autoCreateUser;
|
this.autoCreateUser = autoCreateUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUseAsUsername() {
|
public String getUseAsUsername() {
|
||||||
if (useAsUsername != null && useAsUsername.trim().length() > 0) {
|
return useAsUsername;
|
||||||
return useAsUsername;
|
|
||||||
}
|
|
||||||
return "email";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUseAsUsername(String useAsUsername) {
|
public void setUseAsUsername(String useAsUsername) {
|
||||||
@@ -293,14 +291,44 @@ public class ApplicationProperties {
|
|||||||
return scopes;
|
return scopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setScopes(String scpoes) {
|
public void setScopes(String scopes) {
|
||||||
List<String> scopesList =
|
List<String> scopesList =
|
||||||
Arrays.stream(scpoes.split(","))
|
Arrays.stream(scopes.split(","))
|
||||||
.map(String::trim)
|
.map(String::trim)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
this.scopes.addAll(scopesList);
|
this.scopes.addAll(scopesList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Client getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClient(Client client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isValid(String value, String name) {
|
||||||
|
if (value != null && !value.trim().isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isValid(Collection<String> value, String name) {
|
||||||
|
if (value != null && !value.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSettingsValid() {
|
||||||
|
return isValid(this.getIssuer(), "issuer")
|
||||||
|
&& isValid(this.getClientId(), "clientId")
|
||||||
|
&& isValid(this.getClientSecret(), "clientSecret")
|
||||||
|
&& isValid(this.getScopes(), "scopes")
|
||||||
|
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "OAUTH2 [enabled="
|
return "OAUTH2 [enabled="
|
||||||
@@ -315,12 +343,353 @@ public class ApplicationProperties {
|
|||||||
+ autoCreateUser
|
+ autoCreateUser
|
||||||
+ ", useAsUsername="
|
+ ", useAsUsername="
|
||||||
+ useAsUsername
|
+ useAsUsername
|
||||||
+ ", provider"
|
+ ", provider="
|
||||||
+ provider
|
+ provider
|
||||||
+ ", scopes="
|
+ ", scopes="
|
||||||
+ scopes
|
+ scopes
|
||||||
+ "]";
|
+ "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Client {
|
||||||
|
private GoogleProvider google = new GoogleProvider();
|
||||||
|
private GithubProvider github = new GithubProvider();
|
||||||
|
private KeycloakProvider keycloak = new KeycloakProvider();
|
||||||
|
|
||||||
|
public Provider get(String registrationId) throws Exception {
|
||||||
|
switch (registrationId) {
|
||||||
|
case "gogole":
|
||||||
|
return getGoogle();
|
||||||
|
case "github":
|
||||||
|
return getGithub();
|
||||||
|
case "keycloak":
|
||||||
|
return getKeycloak();
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
throw new Exception("Provider not supported, use custom setting.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public GoogleProvider getGoogle() {
|
||||||
|
return google;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGoogle(GoogleProvider google) {
|
||||||
|
this.google = google;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GithubProvider getGithub() {
|
||||||
|
return github;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGithub(GithubProvider github) {
|
||||||
|
this.github = github;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakProvider getKeycloak() {
|
||||||
|
return keycloak;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKeycloak(KeycloakProvider keycloak) {
|
||||||
|
this.keycloak = keycloak;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Client [google="
|
||||||
|
+ google
|
||||||
|
+ ", github="
|
||||||
|
+ github
|
||||||
|
+ ", keycloak="
|
||||||
|
+ keycloak
|
||||||
|
+ "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GoogleProvider extends Provider {
|
||||||
|
|
||||||
|
private static final String authorizationUri =
|
||||||
|
"https://accounts.google.com/o/oauth2/v2/auth";
|
||||||
|
private static final String tokenUri = "https://www.googleapis.com/oauth2/v4/token";
|
||||||
|
private static final String userInfoUri =
|
||||||
|
"https://www.googleapis.com/oauth2/v3/userinfo?alt=json";
|
||||||
|
|
||||||
|
public String getAuthorizationuri() {
|
||||||
|
return authorizationUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTokenuri() {
|
||||||
|
return tokenUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserinfouri() {
|
||||||
|
return userInfoUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String clientId;
|
||||||
|
private String clientSecret;
|
||||||
|
private Collection<String> scopes = new ArrayList<>();
|
||||||
|
private String useAsUsername = "email";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientId() {
|
||||||
|
return this.clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientSecret() {
|
||||||
|
return this.clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientSecret(String clientSecret) {
|
||||||
|
this.clientSecret = clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getScopes() {
|
||||||
|
if (scopes == null || scopes.isEmpty()) {
|
||||||
|
scopes.add("https://www.googleapis.com/auth/userinfo.email");
|
||||||
|
scopes.add("https://www.googleapis.com/auth/userinfo.profile");
|
||||||
|
}
|
||||||
|
return scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScopes(String scopes) {
|
||||||
|
this.scopes =
|
||||||
|
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUseAsUsername() {
|
||||||
|
return this.useAsUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUseAsUsername(String useAsUsername) {
|
||||||
|
this.useAsUsername = useAsUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Google [clientId="
|
||||||
|
+ clientId
|
||||||
|
+ ", clientSecret="
|
||||||
|
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||||
|
+ ", scopes="
|
||||||
|
+ scopes
|
||||||
|
+ ", useAsUsername="
|
||||||
|
+ useAsUsername
|
||||||
|
+ "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "google";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSettingsValid() {
|
||||||
|
return super.isValid(this.getClientId(), "clientId")
|
||||||
|
&& super.isValid(this.getClientSecret(), "clientSecret")
|
||||||
|
&& super.isValid(this.getScopes(), "scopes")
|
||||||
|
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GithubProvider extends Provider {
|
||||||
|
private static final String authorizationUri = "https://github.com/login/oauth/authorize";
|
||||||
|
private static final String tokenUri = "https://github.com/login/oauth/access_token";
|
||||||
|
private static final String userInfoUri = "https://api.github.com/user";
|
||||||
|
|
||||||
|
public String getAuthorizationuri() {
|
||||||
|
return authorizationUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTokenuri() {
|
||||||
|
return tokenUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserinfouri() {
|
||||||
|
return userInfoUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String clientId;
|
||||||
|
private String clientSecret;
|
||||||
|
private Collection<String> scopes = new ArrayList<>();
|
||||||
|
private String useAsUsername = "login";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIssuer() {
|
||||||
|
return new String();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setIssuer(String issuer) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientId() {
|
||||||
|
return this.clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientSecret() {
|
||||||
|
return this.clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientSecret(String clientSecret) {
|
||||||
|
this.clientSecret = clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> getScopes() {
|
||||||
|
if (scopes == null || scopes.isEmpty()) {
|
||||||
|
scopes.add("read:user");
|
||||||
|
}
|
||||||
|
return scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScopes(String scopes) {
|
||||||
|
this.scopes =
|
||||||
|
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUseAsUsername() {
|
||||||
|
return this.useAsUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUseAsUsername(String useAsUsername) {
|
||||||
|
this.useAsUsername = useAsUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "GitHub [clientId="
|
||||||
|
+ clientId
|
||||||
|
+ ", clientSecret="
|
||||||
|
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||||
|
+ ", scopes="
|
||||||
|
+ scopes
|
||||||
|
+ ", useAsUsername="
|
||||||
|
+ useAsUsername
|
||||||
|
+ "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "github";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSettingsValid() {
|
||||||
|
return super.isValid(this.getClientId(), "clientId")
|
||||||
|
&& super.isValid(this.getClientSecret(), "clientSecret")
|
||||||
|
&& super.isValid(this.getScopes(), "scopes")
|
||||||
|
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class KeycloakProvider extends Provider {
|
||||||
|
private String issuer;
|
||||||
|
private String clientId;
|
||||||
|
private String clientSecret;
|
||||||
|
private Collection<String> scopes = new ArrayList<>();
|
||||||
|
private String useAsUsername = "email";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIssuer() {
|
||||||
|
return this.issuer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setIssuer(String issuer) {
|
||||||
|
this.issuer = issuer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientId() {
|
||||||
|
return this.clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientSecret() {
|
||||||
|
return this.clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientSecret(String clientSecret) {
|
||||||
|
this.clientSecret = clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getScopes() {
|
||||||
|
if (scopes == null || scopes.isEmpty()) {
|
||||||
|
scopes.add("openid");
|
||||||
|
scopes.add("profile");
|
||||||
|
scopes.add("email");
|
||||||
|
}
|
||||||
|
return scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScopes(String scopes) {
|
||||||
|
this.scopes =
|
||||||
|
Arrays.stream(scopes.split(",")).map(String::trim).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUseAsUsername() {
|
||||||
|
return this.useAsUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUseAsUsername(String useAsUsername) {
|
||||||
|
this.useAsUsername = useAsUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Keycloak [issuer="
|
||||||
|
+ issuer
|
||||||
|
+ ", clientId="
|
||||||
|
+ clientId
|
||||||
|
+ ", clientSecret="
|
||||||
|
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||||
|
+ ", scopes="
|
||||||
|
+ scopes
|
||||||
|
+ ", useAsUsername="
|
||||||
|
+ useAsUsername
|
||||||
|
+ "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "keycloak";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSettingsValid() {
|
||||||
|
return isValid(this.getIssuer(), "issuer")
|
||||||
|
&& isValid(this.getClientId(), "clientId")
|
||||||
|
&& isValid(this.getClientSecret(), "clientSecret")
|
||||||
|
&& isValid(this.getScopes(), "scopes")
|
||||||
|
&& isValid(this.getUseAsUsername(), "useAsUsername");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,9 +750,6 @@ public class ApplicationProperties {
|
|||||||
this.googlevisibility = googlevisibility;
|
this.googlevisibility = googlevisibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "System [defaultLocale="
|
return "System [defaultLocale="
|
||||||
|
|||||||
87
src/main/java/stirling/software/SPDF/model/Provider.java
Normal file
87
src/main/java/stirling/software/SPDF/model/Provider.java
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public class Provider implements ProviderInterface {
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isValid(String value, String name) {
|
||||||
|
if (value != null && !value.trim().isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
// throw new IllegalArgumentException(getName() + ": " + name + " is required!");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isValid(Collection<String> value, String name) {
|
||||||
|
if (value != null && !value.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
// throw new IllegalArgumentException(getName() + ": " + name + " is required!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getScopes() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getScope'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScopes(String scopes) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'setScope'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUseAsUsername() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getUseAsUsername'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUseAsUsername(String useAsUsername) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'setUseAsUsername'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIssuer() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getIssuer'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setIssuer(String issuer) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'setIssuer'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientSecret() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getClientSecret'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientSecret(String clientSecret) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'setClientSecret'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientId() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'getClientId'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
throw new UnsupportedOperationException("Unimplemented method 'setClientId'");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public interface ProviderInterface {
|
||||||
|
|
||||||
|
public Collection<String> getScopes();
|
||||||
|
|
||||||
|
public void setScopes(String scopes);
|
||||||
|
|
||||||
|
public String getUseAsUsername();
|
||||||
|
|
||||||
|
public void setUseAsUsername(String useAsUsername);
|
||||||
|
|
||||||
|
public String getIssuer();
|
||||||
|
|
||||||
|
public void setIssuer(String issuer);
|
||||||
|
|
||||||
|
public String getClientSecret();
|
||||||
|
|
||||||
|
public void setClientSecret(String clientSecret);
|
||||||
|
|
||||||
|
public String getClientId();
|
||||||
|
|
||||||
|
public void setClientId(String clientId);
|
||||||
|
}
|
||||||
@@ -79,8 +79,8 @@ public class FileToPdf {
|
|||||||
} finally {
|
} finally {
|
||||||
|
|
||||||
// Clean up temporary files
|
// Clean up temporary files
|
||||||
Files.delete(tempOutputFile);
|
Files.deleteIfExists(tempOutputFile);
|
||||||
Files.delete(tempInputFile);
|
Files.deleteIfExists(tempInputFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
return pdfBytes;
|
return pdfBytes;
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ public class GeneralUtils {
|
|||||||
@Override
|
@Override
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Files.delete(file);
|
Files.deleteIfExists(file);
|
||||||
return FileVisitResult.CONTINUE;
|
return FileVisitResult.CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
|
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Files.delete(dir);
|
Files.deleteIfExists(dir);
|
||||||
return FileVisitResult.CONTINUE;
|
return FileVisitResult.CONTINUE;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import java.io.FileInputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardCopyOption;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -34,7 +33,10 @@ public class PDFToFile {
|
|||||||
|
|
||||||
// Get the original PDF file name without the extension
|
// Get the original PDF file name without the extension
|
||||||
String originalPdfFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
|
String originalPdfFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
|
||||||
String pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
|
String pdfBaseName = originalPdfFileName;
|
||||||
|
if (originalPdfFileName.contains(".")) {
|
||||||
|
pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
|
||||||
|
}
|
||||||
|
|
||||||
Path tempInputFile = null;
|
Path tempInputFile = null;
|
||||||
Path tempOutputDir = null;
|
Path tempOutputDir = null;
|
||||||
@@ -44,8 +46,7 @@ public class PDFToFile {
|
|||||||
try {
|
try {
|
||||||
// Save the uploaded file to a temporary location
|
// Save the uploaded file to a temporary location
|
||||||
tempInputFile = Files.createTempFile("input_", ".pdf");
|
tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||||
Files.copy(
|
inputFile.transferTo(tempInputFile);
|
||||||
inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
|
|
||||||
// Prepare the output directory
|
// Prepare the output directory
|
||||||
tempOutputDir = Files.createTempDirectory("output_");
|
tempOutputDir = Files.createTempDirectory("output_");
|
||||||
@@ -82,7 +83,7 @@ public class PDFToFile {
|
|||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up the temporary files
|
// Clean up the temporary files
|
||||||
if (tempInputFile != null) Files.delete(tempInputFile);
|
if (tempInputFile != null) Files.deleteIfExists(tempInputFile);
|
||||||
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,8 +101,15 @@ public class PDFToFile {
|
|||||||
|
|
||||||
// Get the original PDF file name without the extension
|
// Get the original PDF file name without the extension
|
||||||
String originalPdfFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
|
String originalPdfFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());
|
||||||
String pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
|
|
||||||
|
|
||||||
|
if (originalPdfFileName == null || "".equals(originalPdfFileName.trim())) {
|
||||||
|
originalPdfFileName = "output.pdf";
|
||||||
|
}
|
||||||
|
// Assume file is pdf if no extension
|
||||||
|
String pdfBaseName = originalPdfFileName;
|
||||||
|
if (originalPdfFileName.contains(".")) {
|
||||||
|
pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));
|
||||||
|
}
|
||||||
// Validate output format
|
// Validate output format
|
||||||
List<String> allowedFormats =
|
List<String> allowedFormats =
|
||||||
Arrays.asList("doc", "docx", "odt", "ppt", "pptx", "odp", "rtf", "xml", "txt:Text");
|
Arrays.asList("doc", "docx", "odt", "ppt", "pptx", "odp", "rtf", "xml", "txt:Text");
|
||||||
@@ -117,8 +125,7 @@ public class PDFToFile {
|
|||||||
try {
|
try {
|
||||||
// Save the uploaded file to a temporary location
|
// Save the uploaded file to a temporary location
|
||||||
tempInputFile = Files.createTempFile("input_", ".pdf");
|
tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||||
Files.copy(
|
inputFile.transferTo(tempInputFile);
|
||||||
inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
|
|
||||||
// Prepare the output directory
|
// Prepare the output directory
|
||||||
tempOutputDir = Files.createTempDirectory("output_");
|
tempOutputDir = Files.createTempDirectory("output_");
|
||||||
@@ -170,9 +177,10 @@ public class PDFToFile {
|
|||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up the temporary files
|
// Clean up the temporary files
|
||||||
if (tempInputFile != null) Files.delete(tempInputFile);
|
Files.deleteIfExists(tempInputFile);
|
||||||
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||||
}
|
}
|
||||||
|
System.out.println("fileBytes=" + fileBytes.length);
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
|
fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ public class RequestUriUtils {
|
|||||||
public static boolean isStaticResource(String requestURI) {
|
public static boolean isStaticResource(String requestURI) {
|
||||||
|
|
||||||
return requestURI.startsWith("/css/")
|
return requestURI.startsWith("/css/")
|
||||||
|
|| requestURI.startsWith("/fonts/")
|
||||||
|| requestURI.startsWith("/js/")
|
|| requestURI.startsWith("/js/")
|
||||||
|| requestURI.startsWith("/images/")
|
|| requestURI.startsWith("/images/")
|
||||||
|| requestURI.startsWith("/public/")
|
|| requestURI.startsWith("/public/")
|
||||||
|
|||||||
15
src/main/java/stirling/software/SPDF/utils/UrlUtils.java
Normal file
15
src/main/java/stirling/software/SPDF/utils/UrlUtils.java
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package stirling.software.SPDF.utils;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
public class UrlUtils {
|
||||||
|
|
||||||
|
public static String getOrigin(HttpServletRequest request) {
|
||||||
|
String scheme = request.getScheme(); // http or https
|
||||||
|
String serverName = request.getServerName(); // localhost
|
||||||
|
int serverPort = request.getServerPort(); // 8080
|
||||||
|
String contextPath = request.getContextPath(); // /myapp
|
||||||
|
|
||||||
|
return scheme + "://" + serverName + ":" + serverPort + contextPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -452,6 +452,12 @@ login.locked=Your account has been locked.
|
|||||||
login.signinTitle=Please sign in
|
login.signinTitle=Please sign in
|
||||||
login.ssoSignIn=تسجيل الدخول عبر تسجيل الدخول الأحادي
|
login.ssoSignIn=تسجيل الدخول عبر تسجيل الدخول الأحادي
|
||||||
login.oauth2AutoCreateDisabled=تم تعطيل مستخدم الإنشاء التلقائي لـ OAuth2
|
login.oauth2AutoCreateDisabled=تم تعطيل مستخدم الإنشاء التلقائي لـ OAuth2
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Crop
|
crop.title=Crop
|
||||||
crop.header=Crop Image
|
crop.header=Crop PDF
|
||||||
crop.submit=Submit
|
crop.submit=Submit
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Вашият акаунт е заключен.
|
|||||||
login.signinTitle=Моля впишете се
|
login.signinTitle=Моля впишете се
|
||||||
login.ssoSignIn=Влизане чрез еднократно влизане
|
login.ssoSignIn=Влизане чрез еднократно влизане
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Автоматично създаване на потребител е деактивирано
|
login.oauth2AutoCreateDisabled=OAUTH2 Автоматично създаване на потребител е деактивирано
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Изтегли
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Изрязване
|
crop.title=Изрязване
|
||||||
crop.header=Изрязване на изображение
|
crop.header=Изрязване на PDF
|
||||||
crop.submit=Подайте
|
crop.submit=Подайте
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Compte bloquejat
|
|||||||
login.signinTitle=Autenticat
|
login.signinTitle=Autenticat
|
||||||
login.ssoSignIn=Inicia sessió mitjançant l'inici de sessió ún
|
login.ssoSignIn=Inicia sessió mitjançant l'inici de sessió ún
|
||||||
login.oauth2AutoCreateDisabled=L'usuari de creació automàtica OAUTH2 està desactivat
|
login.oauth2AutoCreateDisabled=L'usuari de creació automàtica OAUTH2 està desactivat
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Talla
|
crop.title=Talla
|
||||||
crop.header=Talla Imatge
|
crop.header=Talla PDF
|
||||||
crop.submit=Submit
|
crop.submit=Submit
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -59,8 +59,8 @@ deleteCurrentUserMessage=Der aktuell angemeldete Benutzer kann nicht gelöscht w
|
|||||||
deleteUsernameExistsMessage=Der Benutzername existiert nicht und kann nicht gelöscht werden.
|
deleteUsernameExistsMessage=Der Benutzername existiert nicht und kann nicht gelöscht werden.
|
||||||
downgradeCurrentUserMessage=Die Rolle des aktuellen Benutzers kann nicht herabgestuft werden
|
downgradeCurrentUserMessage=Die Rolle des aktuellen Benutzers kann nicht herabgestuft werden
|
||||||
downgradeCurrentUserLongMessage=Die Rolle des aktuellen Benutzers kann nicht herabgestuft werden. Daher wird der aktuelle Benutzer nicht angezeigt.
|
downgradeCurrentUserLongMessage=Die Rolle des aktuellen Benutzers kann nicht herabgestuft werden. Daher wird der aktuelle Benutzer nicht angezeigt.
|
||||||
userAlreadyExistsOAuthMessage=The user already exists as an OAuth2 user.
|
userAlreadyExistsOAuthMessage=Der Benutzer ist bereits als OAuth2-Benutzer vorhanden.
|
||||||
userAlreadyExistsWebMessage=The user already exists as an web user.
|
userAlreadyExistsWebMessage=Der Benutzer ist bereits als Webbenutzer vorhanden.
|
||||||
error=Fehler
|
error=Fehler
|
||||||
oops=Hoppla!
|
oops=Hoppla!
|
||||||
help=Hilfe
|
help=Hilfe
|
||||||
@@ -71,7 +71,7 @@ visitGithub=GitHub-Repository besuchen
|
|||||||
donate=Spenden
|
donate=Spenden
|
||||||
color=Farbe
|
color=Farbe
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Info
|
info=Die Info
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -105,18 +105,18 @@ pipelineOptions.validateButton=Validieren
|
|||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
#############
|
#############
|
||||||
navbar.favorite=Favorites
|
navbar.favorite=Favoriten
|
||||||
navbar.darkmode=Dunkler Modus
|
navbar.darkmode=Dunkler Modus
|
||||||
navbar.language=Languages
|
navbar.language=Sprachen
|
||||||
navbar.settings=Einstellungen
|
navbar.settings=Einstellungen
|
||||||
navbar.allTools=Tools
|
navbar.allTools=Werkzeuge
|
||||||
navbar.multiTool=Multi Tools
|
navbar.multiTool=Multitools
|
||||||
navbar.sections.organize=Organize
|
navbar.sections.organize=Organisieren
|
||||||
navbar.sections.convertTo=Convert to PDF
|
navbar.sections.convertTo=In PDF konvertieren
|
||||||
navbar.sections.convertFrom=Convert from PDF
|
navbar.sections.convertFrom=Konvertieren von PDF
|
||||||
navbar.sections.security=Sign & Security
|
navbar.sections.security=Zeichen und Sicherheit
|
||||||
navbar.sections.advance=Advanced
|
navbar.sections.advance=Fortschrittlich
|
||||||
navbar.sections.edit=View & Edit
|
navbar.sections.edit=Anzeigen und Bearbeiten
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
@@ -185,7 +185,7 @@ adminUserSettings.internalApiUser=Interner API-Benutzer
|
|||||||
adminUserSettings.forceChange=Benutzer dazu zwingen, Benutzernamen/Passwort bei der Anmeldung zu ändern
|
adminUserSettings.forceChange=Benutzer dazu zwingen, Benutzernamen/Passwort bei der Anmeldung zu ändern
|
||||||
adminUserSettings.submit=Benutzer speichern
|
adminUserSettings.submit=Benutzer speichern
|
||||||
adminUserSettings.changeUserRole=Benutzerrolle ändern
|
adminUserSettings.changeUserRole=Benutzerrolle ändern
|
||||||
adminUserSettings.authenticated=Authenticated
|
adminUserSettings.authenticated=Authentifiziert
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -452,6 +452,12 @@ login.locked=Ihr Konto wurde gesperrt.
|
|||||||
login.signinTitle=Bitte melden Sie sich an.
|
login.signinTitle=Bitte melden Sie sich an.
|
||||||
login.ssoSignIn=Anmeldung per Single Sign-On
|
login.ssoSignIn=Anmeldung per Single Sign-On
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Benutzer automatisch erstellen deaktiviert
|
login.oauth2AutoCreateDisabled=OAUTH2 Benutzer automatisch erstellen deaktiviert
|
||||||
|
login.oauth2RequestNotFound=Autorisierungsanfrage nicht gefunden
|
||||||
|
login.oauth2InvalidUserInfoResponse=Ungültige Benutzerinformationsantwort
|
||||||
|
login.oauth2invalidRequest=ungültige Anfrage
|
||||||
|
login.oauth2AccessDenied=Zugriff abgelehnt
|
||||||
|
login.oauth2InvalidTokenResponse=Ungültige Token-Antwort
|
||||||
|
login.oauth2InvalidIdToken=Ungültiges ID-Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Herunterladen
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Zuschneiden
|
crop.title=Zuschneiden
|
||||||
crop.header=Bild zuschneiden
|
crop.header=PDF zuschneiden
|
||||||
crop.submit=Abschicken
|
crop.submit=Abschicken
|
||||||
|
|
||||||
|
|
||||||
@@ -704,7 +710,7 @@ repair.submit=Reparieren
|
|||||||
#flatten
|
#flatten
|
||||||
flatten.title=Abflachen
|
flatten.title=Abflachen
|
||||||
flatten.header=PDFs reduzieren
|
flatten.header=PDFs reduzieren
|
||||||
flatten.flattenOnlyForms=Flatten only forms
|
flatten.flattenOnlyForms=Nur Formulare abflachen
|
||||||
flatten.submit=Abflachen
|
flatten.submit=Abflachen
|
||||||
|
|
||||||
|
|
||||||
@@ -752,7 +758,7 @@ extractImages.submit=Extrahieren
|
|||||||
fileToPDF.title=Datei in PDF
|
fileToPDF.title=Datei in PDF
|
||||||
fileToPDF.header=Beliebige Dateien in PDF konvertieren
|
fileToPDF.header=Beliebige Dateien in PDF konvertieren
|
||||||
fileToPDF.credit=Dieser Dienst verwendet LibreOffice und Unoconv für die Dateikonvertierung.
|
fileToPDF.credit=Dieser Dienst verwendet LibreOffice und Unoconv für die Dateikonvertierung.
|
||||||
fileToPDF.supportedFileTypesInfo=Supported File types
|
fileToPDF.supportedFileTypesInfo=Unterstützte Dateitypen
|
||||||
fileToPDF.supportedFileTypes=Unterstützte Dateitypen sollten die folgenden enthalten, eine vollständige aktualisierte Liste der unterstützten Formate finden Sie jedoch in der LibreOffice-Dokumentation
|
fileToPDF.supportedFileTypes=Unterstützte Dateitypen sollten die folgenden enthalten, eine vollständige aktualisierte Liste der unterstützten Formate finden Sie jedoch in der LibreOffice-Dokumentation
|
||||||
fileToPDF.submit=In PDF konvertieren
|
fileToPDF.submit=In PDF konvertieren
|
||||||
|
|
||||||
@@ -805,7 +811,7 @@ pdfOrganiser.placeholder=(z.B. 1,3,2 oder 4-8,2,10-12 oder 2n-1)
|
|||||||
#multiTool
|
#multiTool
|
||||||
multiTool.title=PDF-Multitool
|
multiTool.title=PDF-Multitool
|
||||||
multiTool.header=PDF-Multitool
|
multiTool.header=PDF-Multitool
|
||||||
multiTool.uploadPrompts=File Name
|
multiTool.uploadPrompts=Dateiname
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=PDF anzeigen
|
viewPdf.title=PDF anzeigen
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Ο λογαριασμός σας έχει κλειδωθεί.
|
|||||||
login.signinTitle=Παρακαλώ, συνδεθείτε
|
login.signinTitle=Παρακαλώ, συνδεθείτε
|
||||||
login.ssoSignIn=Σύνδεση μέσω μοναδικής σύνδεσης
|
login.ssoSignIn=Σύνδεση μέσω μοναδικής σύνδεσης
|
||||||
login.oauth2AutoCreateDisabled=Απενεργοποιήθηκε ο χρήστης αυτόματης δημιουργίας OAUTH2
|
login.oauth2AutoCreateDisabled=Απενεργοποιήθηκε ο χρήστης αυτόματης δημιουργίας OAUTH2
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Λήψη
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Κοπή
|
crop.title=Κοπή
|
||||||
crop.header=Κοπή Εικόνας
|
crop.header=Περικοπή PDF
|
||||||
crop.submit=Υποβολή
|
crop.submit=Υποβολή
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Your account has been locked.
|
|||||||
login.signinTitle=Please sign in
|
login.signinTitle=Please sign in
|
||||||
login.ssoSignIn=Login via Single Sign-on
|
login.ssoSignIn=Login via Single Sign-on
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Crop
|
crop.title=Crop
|
||||||
crop.header=Crop Image
|
crop.header=Crop PDF
|
||||||
crop.submit=Submit
|
crop.submit=Submit
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Your account has been locked.
|
|||||||
login.signinTitle=Please sign in
|
login.signinTitle=Please sign in
|
||||||
login.ssoSignIn=Login via Single Sign-on
|
login.ssoSignIn=Login via Single Sign-on
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Crop
|
crop.title=Crop
|
||||||
crop.header=Crop Image
|
crop.header=Crop PDF
|
||||||
crop.submit=Submit
|
crop.submit=Submit
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Su cuenta se ha bloqueado.
|
|||||||
login.signinTitle=Por favor, inicie sesión
|
login.signinTitle=Por favor, inicie sesión
|
||||||
login.ssoSignIn=Iniciar sesión a través del inicio de sesión único
|
login.ssoSignIn=Iniciar sesión a través del inicio de sesión único
|
||||||
login.oauth2AutoCreateDisabled=Usuario DE creación automática de OAUTH2 DESACTIVADO
|
login.oauth2AutoCreateDisabled=Usuario DE creación automática de OAUTH2 DESACTIVADO
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Descargar
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Recortar
|
crop.title=Recortar
|
||||||
crop.header=Recortar Imagen
|
crop.header=Recortar PDF
|
||||||
crop.submit=Entregar
|
crop.submit=Entregar
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Zure kontua blokeatu egin da.
|
|||||||
login.signinTitle=Mesedez, hasi saioa
|
login.signinTitle=Mesedez, hasi saioa
|
||||||
login.ssoSignIn=Hasi saioa Saioa hasteko modu bakarraren bidez
|
login.ssoSignIn=Hasi saioa Saioa hasteko modu bakarraren bidez
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Sortu automatikoki erabiltzailea desgaituta dago
|
login.oauth2AutoCreateDisabled=OAUTH2 Sortu automatikoki erabiltzailea desgaituta dago
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Distira
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Moztu
|
crop.title=Moztu
|
||||||
crop.header=Irudia Moztu
|
crop.header=Moztu PDF
|
||||||
crop.submit=Bidali
|
crop.submit=Bidali
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,18 +22,18 @@ close=Fermer
|
|||||||
filesSelected=fichiers sélectionnés
|
filesSelected=fichiers sélectionnés
|
||||||
noFavourites=Aucun favori ajouté
|
noFavourites=Aucun favori ajouté
|
||||||
downloadComplete=Téléchargement terminé
|
downloadComplete=Téléchargement terminé
|
||||||
bored=Ennuyé d’attendre ?
|
bored=Marre d’attendre ?
|
||||||
alphabet=Alphabet
|
alphabet=Alphabet
|
||||||
downloadPdf=Télécharger le PDF
|
downloadPdf=Télécharger le PDF
|
||||||
text=Texte
|
text=Texte
|
||||||
font=Police
|
font=Police
|
||||||
selectFillter=-- Sélectionnez --
|
selectFillter=-- Sélectionnez --
|
||||||
pageNum=numéro de page
|
pageNum=Numéro de page
|
||||||
sizes.small=Petit
|
sizes.small=Petit
|
||||||
sizes.medium=Moyen
|
sizes.medium=Moyen
|
||||||
sizes.large=Grand
|
sizes.large=Grand
|
||||||
sizes.x-large=Très grand
|
sizes.x-large=Très grand
|
||||||
error.pdfPassword=Le document PDF est protégé par un mot de passe et le mot de passe n’a pas été fourni ou était incorrect
|
error.pdfPassword=Le document PDF est protégé par un mot de passe qui n’a pas été fourni ou était incorrect
|
||||||
delete=Supprimer
|
delete=Supprimer
|
||||||
username=Nom d’utilisateur
|
username=Nom d’utilisateur
|
||||||
password=Mot de passe
|
password=Mot de passe
|
||||||
@@ -78,13 +78,13 @@ info=Info
|
|||||||
###############
|
###############
|
||||||
# Pipeline #
|
# Pipeline #
|
||||||
###############
|
###############
|
||||||
pipeline.header=Menu Pipeline (Alpha)
|
pipeline.header=Menu Pipeline (Beta)
|
||||||
pipeline.uploadButton=Charger une personnalisation
|
pipeline.uploadButton=Charger une personnalisation
|
||||||
pipeline.configureButton=Configurer
|
pipeline.configureButton=Configurer
|
||||||
pipeline.defaultOption=Personnaliser
|
pipeline.defaultOption=Personnaliser
|
||||||
pipeline.submitButton=Soumettre
|
pipeline.submitButton=Soumettre
|
||||||
pipeline.help=Pipeline Help
|
pipeline.help=Aide Pipeline
|
||||||
pipeline.scanHelp=Folder Scanning Help
|
pipeline.scanHelp=Aide analyse de dossier
|
||||||
|
|
||||||
######################
|
######################
|
||||||
# Pipeline Options #
|
# Pipeline Options #
|
||||||
@@ -107,16 +107,16 @@ pipelineOptions.validateButton=Valider
|
|||||||
#############
|
#############
|
||||||
navbar.favorite=Favoris
|
navbar.favorite=Favoris
|
||||||
navbar.darkmode=Mode sombre
|
navbar.darkmode=Mode sombre
|
||||||
navbar.language=Langages
|
navbar.language=Langues
|
||||||
navbar.settings=Paramètres
|
navbar.settings=Paramètres
|
||||||
navbar.allTools=OUtils
|
navbar.allTools=Outils
|
||||||
navbar.multiTool=Multi Outils
|
navbar.multiTool=Outils Multiples
|
||||||
navbar.sections.organize=Oragnisation
|
navbar.sections.organize=Organisation
|
||||||
navbar.sections.convertTo=Convertir en PDF
|
navbar.sections.convertTo=Convertir en PDF
|
||||||
navbar.sections.convertFrom=Convertir depuis PDF
|
navbar.sections.convertFrom=Convertir depuis PDF
|
||||||
navbar.sections.security=Signalisation et sécurité
|
navbar.sections.security=Signature et sécurité
|
||||||
navbar.sections.advance=Mode avancé
|
navbar.sections.advance=Mode avancé
|
||||||
navbar.sections.edit=Voir la modification
|
navbar.sections.edit=Voir et modifier
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
@@ -132,7 +132,7 @@ settings.downloadOption.3=Télécharger le fichier
|
|||||||
settings.zipThreshold=Compresser les fichiers en ZIP lorsque le nombre de fichiers téléchargés dépasse
|
settings.zipThreshold=Compresser les fichiers en ZIP lorsque le nombre de fichiers téléchargés dépasse
|
||||||
settings.signOut=Déconnexion
|
settings.signOut=Déconnexion
|
||||||
settings.accountSettings=Paramètres du compte
|
settings.accountSettings=Paramètres du compte
|
||||||
settings.bored.help=Enables easter egg game
|
settings.bored.help=Activer les jeux cachés
|
||||||
settings.cacheInputs.name=Sauvegarder les entrées du formulaire
|
settings.cacheInputs.name=Sauvegarder les entrées du formulaire
|
||||||
settings.cacheInputs.help=Permet de stocker les entrées précédemment utilisées pour les exécutions futures
|
settings.cacheInputs.help=Permet de stocker les entrées précédemment utilisées pour les exécutions futures
|
||||||
|
|
||||||
@@ -181,7 +181,7 @@ adminUserSettings.apiUser=Utilisateur API limité
|
|||||||
adminUserSettings.extraApiUser=Utilisateur limité supplémentaire de l’API
|
adminUserSettings.extraApiUser=Utilisateur limité supplémentaire de l’API
|
||||||
adminUserSettings.webOnlyUser=Utilisateur Web uniquement
|
adminUserSettings.webOnlyUser=Utilisateur Web uniquement
|
||||||
adminUserSettings.demoUser=Demo User (Paramètres par défaut)
|
adminUserSettings.demoUser=Demo User (Paramètres par défaut)
|
||||||
adminUserSettings.internalApiUser=Internal API User
|
adminUserSettings.internalApiUser=Utilisateur de l'API interne
|
||||||
adminUserSettings.forceChange=Forcer l’utilisateur à changer son nom d’utilisateur/mot de passe lors de la connexion
|
adminUserSettings.forceChange=Forcer l’utilisateur à changer son nom d’utilisateur/mot de passe lors de la connexion
|
||||||
adminUserSettings.submit=Ajouter
|
adminUserSettings.submit=Ajouter
|
||||||
adminUserSettings.changeUserRole=Changer le rôle de l'utilisateur
|
adminUserSettings.changeUserRole=Changer le rôle de l'utilisateur
|
||||||
@@ -357,7 +357,7 @@ home.adjust-contrast.desc=Ajustez le contraste, la saturation et la luminosité
|
|||||||
adjust-contrast.tags=ajuster,couleurs,amélioration,color-correction,tune,modify,enhance
|
adjust-contrast.tags=ajuster,couleurs,amélioration,color-correction,tune,modify,enhance
|
||||||
|
|
||||||
home.crop.title=Redimensionner
|
home.crop.title=Redimensionner
|
||||||
home.crop.desc=Redimmensionnez un PDF pour réduire sa taille (en conservant le texte !).
|
home.crop.desc=Redimensionnez un PDF pour réduire sa taille (en conservant le texte !).
|
||||||
crop.tags=redimensionner,trim,shrink,edit,shape
|
crop.tags=redimensionner,trim,shrink,edit,shape
|
||||||
|
|
||||||
home.autoSplitPDF.title=Séparer automatiquement les pages
|
home.autoSplitPDF.title=Séparer automatiquement les pages
|
||||||
@@ -428,11 +428,11 @@ home.AddStampRequest.desc=Ajouter un texte ou l’image d’un tampon à un empl
|
|||||||
AddStampRequest.tags=Tampon,Ajouter,Stamp,Add image,center image,Watermark,PDF,Embed,Customize
|
AddStampRequest.tags=Tampon,Ajouter,Stamp,Add image,center image,Watermark,PDF,Embed,Customize
|
||||||
|
|
||||||
|
|
||||||
home.PDFToBook.title=PDF to Book
|
home.PDFToBook.title=PDF vers eBook
|
||||||
home.PDFToBook.desc=Convertit le PDF en formats livre/bande dessinée à l'aide de calibre
|
home.PDFToBook.desc=Convertit le PDF en formats livre/bande dessinée à l'aide de calibre
|
||||||
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||||
|
|
||||||
home.BookToPDF.title=Book to PDF
|
home.BookToPDF.title=eBook vers PDF
|
||||||
home.BookToPDF.desc=Convertit les formats de livres/bandes dessinées en PDF à l'aide de calibre
|
home.BookToPDF.desc=Convertit les formats de livres/bandes dessinées en PDF à l'aide de calibre
|
||||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||||
|
|
||||||
@@ -452,6 +452,12 @@ login.locked=Votre compte a été verrouillé.
|
|||||||
login.signinTitle=Veuillez vous connecter
|
login.signinTitle=Veuillez vous connecter
|
||||||
login.ssoSignIn=Se connecter via l'authentification unique
|
login.ssoSignIn=Se connecter via l'authentification unique
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Création automatique d'utilisateur désactivée
|
login.oauth2AutoCreateDisabled=OAUTH2 Création automatique d'utilisateur désactivée
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -475,8 +481,8 @@ showJS.submit=Afficher
|
|||||||
|
|
||||||
|
|
||||||
#pdfToSinglePage
|
#pdfToSinglePage
|
||||||
pdfToSinglePage.title=Fusionner des pages
|
pdfToSinglePage.title=Fusionner les pages
|
||||||
pdfToSinglePage.header=Fusionner des pages
|
pdfToSinglePage.header=Fusionner les pages
|
||||||
pdfToSinglePage.submit=Convertir en une seule page
|
pdfToSinglePage.submit=Convertir en une seule page
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=आपका खाता लॉक कर दिया गया
|
|||||||
login.signinTitle=कृपया साइन इन करें
|
login.signinTitle=कृपया साइन इन करें
|
||||||
login.ssoSignIn=सिंगल साइन - ऑन के ज़रिए लॉग इन करें
|
login.ssoSignIn=सिंगल साइन - ऑन के ज़रिए लॉग इन करें
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 ऑटो - क्रिएट यूज़र अक्षम किया गया
|
login.oauth2AutoCreateDisabled=OAUTH2 ऑटो - क्रिएट यूज़र अक्षम किया गया
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=डाउनलोड
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=कटौती
|
crop.title=कटौती
|
||||||
crop.header=छवि काटो
|
crop.header=क्रॉप पीडीएफ़
|
||||||
crop.submit=प्रस्तुत करें
|
crop.submit=प्रस्तुत करें
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=A fiókja zárolva lett!
|
|||||||
login.signinTitle=Kérjük, jelentkezzen be!
|
login.signinTitle=Kérjük, jelentkezzen be!
|
||||||
login.ssoSignIn=Bejelentkezés egyszeri bejelentkezéssel
|
login.ssoSignIn=Bejelentkezés egyszeri bejelentkezéssel
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Felhasználó automatikus létrehozása letiltva
|
login.oauth2AutoCreateDisabled=OAUTH2 Felhasználó automatikus létrehozása letiltva
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Letöltés
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Körülvágás
|
crop.title=Körülvágás
|
||||||
crop.header=Kép körülvégésa
|
crop.header=Crop PDF
|
||||||
crop.submit=Elküldés
|
crop.submit=Elküldés
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Akun Anda telah dikunci.
|
|||||||
login.signinTitle=Silakan masuk
|
login.signinTitle=Silakan masuk
|
||||||
login.ssoSignIn=Masuk melalui Single Sign - on
|
login.ssoSignIn=Masuk melalui Single Sign - on
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Buat Otomatis Pengguna Dinonaktifkan
|
login.oauth2AutoCreateDisabled=OAUTH2 Buat Otomatis Pengguna Dinonaktifkan
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Unduh
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Pangkas
|
crop.title=Pangkas
|
||||||
crop.header=Pangkas Gambar
|
crop.header=Pangkas PDF
|
||||||
crop.submit=Kirim
|
crop.submit=Kirim
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Il tuo account è stato bloccato.
|
|||||||
login.signinTitle=Per favore accedi
|
login.signinTitle=Per favore accedi
|
||||||
login.ssoSignIn=Accedi tramite Single Sign-on
|
login.ssoSignIn=Accedi tramite Single Sign-on
|
||||||
login.oauth2AutoCreateDisabled=Creazione automatica utente OAUTH2 DISABILITATA
|
login.oauth2AutoCreateDisabled=Creazione automatica utente OAUTH2 DISABILITATA
|
||||||
|
login.oauth2RequestNotFound=Richiesta di autorizzazione non trovata
|
||||||
|
login.oauth2InvalidUserInfoResponse=Risposta relativa alle informazioni utente non valida
|
||||||
|
login.oauth2invalidRequest=Richiesta non valida
|
||||||
|
login.oauth2AccessDenied=Accesso negato
|
||||||
|
login.oauth2InvalidTokenResponse=Risposta token non valida
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Ritaglia
|
crop.title=Ritaglia
|
||||||
crop.header=Ritaglia l'immagine
|
crop.header=Ritaglia PDF
|
||||||
crop.submit=Invia
|
crop.submit=Invia
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=あなたのアカウントはロックされています。
|
|||||||
login.signinTitle=サインインしてください
|
login.signinTitle=サインインしてください
|
||||||
login.ssoSignIn=シングルサインオンでログイン
|
login.ssoSignIn=シングルサインオンでログイン
|
||||||
login.oauth2AutoCreateDisabled=OAuth 2自動作成ユーザーが無効
|
login.oauth2AutoCreateDisabled=OAuth 2自動作成ユーザーが無効
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=ダウンロード
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=切り抜き
|
crop.title=切り抜き
|
||||||
crop.header=画像の切り抜き
|
crop.header=PDFのトリミング
|
||||||
crop.submit=送信
|
crop.submit=送信
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=계정이 잠겼습니다.
|
|||||||
login.signinTitle=로그인해 주세요.
|
login.signinTitle=로그인해 주세요.
|
||||||
login.ssoSignIn=싱글사인온을 통한 로그인
|
login.ssoSignIn=싱글사인온을 통한 로그인
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 사용자 자동 생성 비활성화됨
|
login.oauth2AutoCreateDisabled=OAUTH2 사용자 자동 생성 비활성화됨
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=다운로드
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=잘라내기
|
crop.title=잘라내기
|
||||||
crop.header=잘라내기
|
crop.header=PDF 잘라내기
|
||||||
crop.submit=확인
|
crop.submit=확인
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Je account is geblokkeerd.
|
|||||||
login.signinTitle=Gelieve in te loggen
|
login.signinTitle=Gelieve in te loggen
|
||||||
login.ssoSignIn=Inloggen via Single Sign-on
|
login.ssoSignIn=Inloggen via Single Sign-on
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Automatisch aanmaken gebruiker uitgeschakeld
|
login.oauth2AutoCreateDisabled=OAUTH2 Automatisch aanmaken gebruiker uitgeschakeld
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Downloaden
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Bijwerken
|
crop.title=Bijwerken
|
||||||
crop.header=Afbeelding bijwerken
|
crop.header=PDF bijsnijden
|
||||||
crop.submit=Indienen
|
crop.submit=Indienen
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Your account has been locked.
|
|||||||
login.signinTitle=Please sign in
|
login.signinTitle=Please sign in
|
||||||
login.ssoSignIn=Zaloguj się za pomocą logowania jednokrotnego
|
login.ssoSignIn=Zaloguj się za pomocą logowania jednokrotnego
|
||||||
login.oauth2AutoCreateDisabled=Wyłączono automatyczne tworzenie użytkownika OAUTH2
|
login.oauth2AutoCreateDisabled=Wyłączono automatyczne tworzenie użytkownika OAUTH2
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Crop
|
crop.title=Crop
|
||||||
crop.header=Crop Image
|
crop.header=Crop PDF
|
||||||
crop.submit=Submit
|
crop.submit=Submit
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Your account has been locked.
|
|||||||
login.signinTitle=Please sign in
|
login.signinTitle=Please sign in
|
||||||
login.ssoSignIn=Iniciar sessão através de início de sessão único
|
login.ssoSignIn=Iniciar sessão através de início de sessão único
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Criar Usuário Desativado
|
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Criar Usuário Desativado
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Cortar
|
crop.title=Cortar
|
||||||
crop.header=Cortar Imagem
|
crop.header=Cortar PDF
|
||||||
crop.submit=Enviar
|
crop.submit=Enviar
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=A sua conta foi bloqueada.
|
|||||||
login.signinTitle=Introduza os seus dados de acesso
|
login.signinTitle=Introduza os seus dados de acesso
|
||||||
login.ssoSignIn=Iniciar sessão através de início de sessão único
|
login.ssoSignIn=Iniciar sessão através de início de sessão único
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Criação Automática de Utilizador Desativada
|
login.oauth2AutoCreateDisabled=OAUTH2 Criação Automática de Utilizador Desativada
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Cortar
|
crop.title=Cortar
|
||||||
crop.header=Cortar Imagem
|
crop.header=Cortar PDF
|
||||||
crop.submit=Enviar
|
crop.submit=Enviar
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Your account has been locked.
|
|||||||
login.signinTitle=Please sign in
|
login.signinTitle=Please sign in
|
||||||
login.ssoSignIn=Conectare prin conectare unică
|
login.ssoSignIn=Conectare prin conectare unică
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Creare automată utilizator dezactivată
|
login.oauth2AutoCreateDisabled=OAUTH2 Creare automată utilizator dezactivată
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Crop
|
crop.title=Crop
|
||||||
crop.header=Crop Image
|
crop.header=Crop PDF
|
||||||
crop.submit=Submit
|
crop.submit=Submit
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Ваша учетная запись заблокирована.
|
|||||||
login.signinTitle=Пожалуйста, войдите
|
login.signinTitle=Пожалуйста, войдите
|
||||||
login.ssoSignIn=Вход через единый вход
|
login.ssoSignIn=Вход через единый вход
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Автоматическое создание пользователя отключено
|
login.oauth2AutoCreateDisabled=OAUTH2 Автоматическое создание пользователя отключено
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Скачать
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Обрезать
|
crop.title=Обрезать
|
||||||
crop.header=Обрезать изображение
|
crop.header=Обрезать PDF-файл
|
||||||
crop.submit=Отправить
|
crop.submit=Отправить
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Váš účet bol uzamknutý.
|
|||||||
login.signinTitle=Prosím, prihláste sa
|
login.signinTitle=Prosím, prihláste sa
|
||||||
login.ssoSignIn=Prihlásiť sa cez Single Sign-on
|
login.ssoSignIn=Prihlásiť sa cez Single Sign-on
|
||||||
login.oauth2AutoCreateDisabled=Vytváranie používateľa cez OAUTH2 je zakázané
|
login.oauth2AutoCreateDisabled=Vytváranie používateľa cez OAUTH2 je zakázané
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Stiahnuť
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Orezať
|
crop.title=Orezať
|
||||||
crop.header=Orezať obrázok
|
crop.header=Orezať PDF
|
||||||
crop.submit=Odoslať
|
crop.submit=Odoslať
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Vaš nalog je zaključan.
|
|||||||
login.signinTitle=Molimo vas da se prijavite
|
login.signinTitle=Molimo vas da se prijavite
|
||||||
login.ssoSignIn=Prijavite se putem jedinstvene prijave
|
login.ssoSignIn=Prijavite se putem jedinstvene prijave
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 automatsko kreiranje korisnika je onemogućeno
|
login.oauth2AutoCreateDisabled=OAUTH2 automatsko kreiranje korisnika je onemogućeno
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Preuzmi
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Iseci
|
crop.title=Iseci
|
||||||
crop.header=Iseci Sliku
|
crop.header=Skraćivanje PDF-a
|
||||||
crop.submit=Potvrdi
|
crop.submit=Potvrdi
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Your account has been locked.
|
|||||||
login.signinTitle=Please sign in
|
login.signinTitle=Please sign in
|
||||||
login.ssoSignIn=Logga in via enkel inloggning
|
login.ssoSignIn=Logga in via enkel inloggning
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User inaktiverad
|
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User inaktiverad
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Download
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Crop
|
crop.title=Crop
|
||||||
crop.header=Crop Image
|
crop.header=Crop PDF
|
||||||
crop.submit=Submit
|
crop.submit=Submit
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Hesabınız kilitlendi.
|
|||||||
login.signinTitle=Lütfen giriş yapınız.
|
login.signinTitle=Lütfen giriş yapınız.
|
||||||
login.ssoSignIn=Tek Oturum Açma ile Giriş Yap
|
login.ssoSignIn=Tek Oturum Açma ile Giriş Yap
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Otomatik Oluşturma Kullanıcı Devre Dışı Bırakıldı
|
login.oauth2AutoCreateDisabled=OAUTH2 Otomatik Oluşturma Kullanıcı Devre Dışı Bırakıldı
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=İndir
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Kırp
|
crop.title=Kırp
|
||||||
crop.header=Resmi Kırp
|
crop.header=PDF'i Kırp
|
||||||
crop.submit=Gönder
|
crop.submit=Gönder
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=Ваш обліковий запис заблоковано.
|
|||||||
login.signinTitle=Будь ласка, увійдіть
|
login.signinTitle=Будь ласка, увійдіть
|
||||||
login.ssoSignIn=Увійти через єдиний вхід
|
login.ssoSignIn=Увійти через єдиний вхід
|
||||||
login.oauth2AutoCreateDisabled=Автоматичне створення користувача OAUTH2 ВИМКНЕНО
|
login.oauth2AutoCreateDisabled=Автоматичне створення користувача OAUTH2 ВИМКНЕНО
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=Завантажити
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=Обрізати
|
crop.title=Обрізати
|
||||||
crop.header=Обрізати зображення
|
crop.header=Обрізати PDF-файл
|
||||||
crop.submit=Надіслати
|
crop.submit=Надіслати
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -105,18 +105,18 @@ pipelineOptions.validateButton=验证
|
|||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
#############
|
#############
|
||||||
navbar.favorite=Favorites
|
navbar.favorite=收藏
|
||||||
navbar.darkmode=暗模式
|
navbar.darkmode=暗黑模式
|
||||||
navbar.language=Languages
|
navbar.language=语言
|
||||||
navbar.settings=设置
|
navbar.settings=设置
|
||||||
navbar.allTools=Tools
|
navbar.allTools=工具箱
|
||||||
navbar.multiTool=Multi Tools
|
navbar.multiTool=多功能工具
|
||||||
navbar.sections.organize=Organize
|
navbar.sections.organize=组织
|
||||||
navbar.sections.convertTo=Convert to PDF
|
navbar.sections.convertTo=转换成PDF
|
||||||
navbar.sections.convertFrom=Convert from PDF
|
navbar.sections.convertFrom=从PDF转换
|
||||||
navbar.sections.security=Sign & Security
|
navbar.sections.security=签名和安全
|
||||||
navbar.sections.advance=Advanced
|
navbar.sections.advance=高级功能
|
||||||
navbar.sections.edit=View & Edit
|
navbar.sections.edit=查看和编辑
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
@@ -452,6 +452,12 @@ login.locked=您的账户已被锁定。
|
|||||||
login.signinTitle=请登录
|
login.signinTitle=请登录
|
||||||
login.ssoSignIn=通过单点登录登录
|
login.ssoSignIn=通过单点登录登录
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2自动创建用户已禁用
|
login.oauth2AutoCreateDisabled=OAUTH2自动创建用户已禁用
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=下载
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=裁剪
|
crop.title=裁剪
|
||||||
crop.header=裁剪图像
|
crop.header=裁剪PDF
|
||||||
crop.submit=提交
|
crop.submit=提交
|
||||||
|
|
||||||
|
|
||||||
@@ -805,7 +811,7 @@ pdfOrganiser.placeholder=(例如 1,3,2 或 4-8,2,10-12 或 2n-1)
|
|||||||
#multiTool
|
#multiTool
|
||||||
multiTool.title=PDF多功能工具
|
multiTool.title=PDF多功能工具
|
||||||
multiTool.header=PDF多功能工具
|
multiTool.header=PDF多功能工具
|
||||||
multiTool.uploadPrompts=File Name
|
multiTool.uploadPrompts=文件名
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=浏览PDF
|
viewPdf.title=浏览PDF
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ login.locked=您的帳戶已被鎖定。
|
|||||||
login.signinTitle=請登入
|
login.signinTitle=請登入
|
||||||
login.ssoSignIn=透過織網單一簽入
|
login.ssoSignIn=透過織網單一簽入
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2自動建立使用者已停用
|
login.oauth2AutoCreateDisabled=OAUTH2自動建立使用者已停用
|
||||||
|
login.oauth2RequestNotFound=Authorization request not found
|
||||||
|
login.oauth2InvalidUserInfoResponse=Invalid User Info Response
|
||||||
|
login.oauth2invalidRequest=Invalid Request
|
||||||
|
login.oauth2AccessDenied=Access Denied
|
||||||
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
@@ -592,7 +598,7 @@ adjustContrast.download=下載
|
|||||||
|
|
||||||
#crop
|
#crop
|
||||||
crop.title=裁剪
|
crop.title=裁剪
|
||||||
crop.header=裁剪影像
|
crop.header=裁剪 PDF
|
||||||
crop.submit=送出
|
crop.submit=送出
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,12 +19,29 @@ security:
|
|||||||
# useAsUsername: "email" # Default is 'email'; custom fields can be used as the username
|
# useAsUsername: "email" # Default is 'email'; custom fields can be used as the username
|
||||||
# scopes: "openid, profile, email" # Specify the scopes for which the application will request permissions
|
# scopes: "openid, profile, email" # Specify the scopes for which the application will request permissions
|
||||||
# provider: "google" # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
# provider: "google" # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
||||||
|
# client:
|
||||||
|
# google:
|
||||||
|
# clientId: "" # Client ID for Google OAuth2
|
||||||
|
# clientSecret: "" # Client Secret for Google OAuth2
|
||||||
|
# scopes: "https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile" # Scopes for Google OAuth2
|
||||||
|
# useAsUsername: "email" # Field to use as the username for Google OAuth2
|
||||||
|
# github:
|
||||||
|
# clientId: "" # Client ID for GitHub OAuth2
|
||||||
|
# clientSecret: "" # Client Secret for GitHub OAuth2
|
||||||
|
# scopes: "read:user" # Scope for GitHub OAuth2
|
||||||
|
# useAsUsername: "login" # Field to use as the username for GitHub OAuth2
|
||||||
|
# keycloak:
|
||||||
|
# issuer: "http://192.168.0.123:8888/realms/stirling-pdf" # URL of the Keycloak realm's OpenID Connect Discovery endpoint
|
||||||
|
# clientId: "stirling-pdf" # Client ID for Keycloak OAuth2
|
||||||
|
# clientSecret: "" # Client Secret for Keycloak OAuth2
|
||||||
|
# scopes: "openid, profile, email" # Scopes for Keycloak OAuth2
|
||||||
|
# useAsUsername: "email" # Field to use as the username for Keycloak OAuth2
|
||||||
|
|
||||||
system:
|
system:
|
||||||
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
||||||
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
|
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
|
||||||
enableAlphaFunctionality: false # Set to enable functionality which might need more testing before it fully goes live (This feature might make no changes)
|
enableAlphaFunctionality: false # Set to enable functionality which might need more testing before it fully goes live (This feature might make no changes)
|
||||||
showUpdate: true # see when a new update is available
|
showUpdate: false # see when a new update is available
|
||||||
showUpdateOnlyAdmin: false # Only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
|
showUpdateOnlyAdmin: false # Only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
|
||||||
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template html files
|
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template html files
|
||||||
|
|
||||||
|
|||||||
@@ -343,6 +343,13 @@
|
|||||||
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
||||||
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "jakarta.servlet:jakarta.servlet-api",
|
||||||
|
"moduleUrl": "https://www.eclipse.org",
|
||||||
|
"moduleVersion": "6.0.0",
|
||||||
|
"moduleLicense": "GPL2 w/ CPE",
|
||||||
|
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "jakarta.transaction:jakarta.transaction-api",
|
"moduleName": "jakarta.transaction:jakarta.transaction-api",
|
||||||
"moduleUrl": "https://projects.eclipse.org/projects/ee4j.jta",
|
"moduleUrl": "https://projects.eclipse.org/projects/ee4j.jta",
|
||||||
@@ -357,6 +364,20 @@
|
|||||||
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
||||||
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "jakarta.websocket:jakarta.websocket-api",
|
||||||
|
"moduleUrl": "https://www.eclipse.org",
|
||||||
|
"moduleVersion": "2.1.1",
|
||||||
|
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
||||||
|
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "jakarta.websocket:jakarta.websocket-client-api",
|
||||||
|
"moduleUrl": "https://www.eclipse.org",
|
||||||
|
"moduleVersion": "2.1.1",
|
||||||
|
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
||||||
|
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "jakarta.xml.bind:jakarta.xml.bind-api",
|
"moduleName": "jakarta.xml.bind:jakarta.xml.bind-api",
|
||||||
"moduleUrl": "https://www.eclipse.org",
|
"moduleUrl": "https://www.eclipse.org",
|
||||||
@@ -452,13 +473,6 @@
|
|||||||
"moduleLicense": "Apache-2.0",
|
"moduleLicense": "Apache-2.0",
|
||||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"moduleName": "org.apache.tomcat.embed:tomcat-embed-core",
|
|
||||||
"moduleUrl": "https://tomcat.apache.org/",
|
|
||||||
"moduleVersion": "10.1.19",
|
|
||||||
"moduleLicense": "Eclipse Public License - v 2.0",
|
|
||||||
"moduleLicenseUrl": "https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "org.apache.tomcat.embed:tomcat-embed-el",
|
"moduleName": "org.apache.tomcat.embed:tomcat-embed-el",
|
||||||
"moduleUrl": "https://tomcat.apache.org/",
|
"moduleUrl": "https://tomcat.apache.org/",
|
||||||
@@ -466,13 +480,6 @@
|
|||||||
"moduleLicense": "Apache License, Version 2.0",
|
"moduleLicense": "Apache License, Version 2.0",
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"moduleName": "org.apache.tomcat.embed:tomcat-embed-websocket",
|
|
||||||
"moduleUrl": "https://tomcat.apache.org/",
|
|
||||||
"moduleVersion": "10.1.19",
|
|
||||||
"moduleLicense": "Apache License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "org.apache.xmlgraphics:batik-all",
|
"moduleName": "org.apache.xmlgraphics:batik-all",
|
||||||
"moduleVersion": "1.17",
|
"moduleVersion": "1.17",
|
||||||
@@ -540,6 +547,188 @@
|
|||||||
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
||||||
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-client",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-common",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-servlet",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-annotations",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-plus",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlet",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlets",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-webapp",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-client",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-common",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-server",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-api",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-common",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty:jetty-alpn-client",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty:jetty-client",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty:jetty-http",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty:jetty-io",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty:jetty-jndi",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty:jetty-plus",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty:jetty-security",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty:jetty-server",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty:jetty-session",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty:jetty-util",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.eclipse.jetty:jetty-xml",
|
||||||
|
"moduleUrl": "https://eclipse.dev/jetty/",
|
||||||
|
"moduleVersion": "12.0.7",
|
||||||
|
"moduleLicense": "Eclipse Public License - Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "org.glassfish.jaxb:jaxb-core",
|
"moduleName": "org.glassfish.jaxb:jaxb-core",
|
||||||
"moduleUrl": "https://www.eclipse.org",
|
"moduleUrl": "https://www.eclipse.org",
|
||||||
@@ -599,7 +788,21 @@
|
|||||||
{
|
{
|
||||||
"moduleName": "org.ow2.asm:asm",
|
"moduleName": "org.ow2.asm:asm",
|
||||||
"moduleUrl": "http://asm.ow2.org",
|
"moduleUrl": "http://asm.ow2.org",
|
||||||
"moduleVersion": "9.3",
|
"moduleVersion": "9.6",
|
||||||
|
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||||
|
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.ow2.asm:asm-commons",
|
||||||
|
"moduleUrl": "http://asm.ow2.org",
|
||||||
|
"moduleVersion": "9.6",
|
||||||
|
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||||
|
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.ow2.asm:asm-tree",
|
||||||
|
"moduleUrl": "http://asm.ow2.org",
|
||||||
|
"moduleVersion": "9.6",
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||||
},
|
},
|
||||||
@@ -705,6 +908,13 @@
|
|||||||
"moduleLicense": "Apache License, Version 2.0",
|
"moduleLicense": "Apache License, Version 2.0",
|
||||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "org.springframework.boot:spring-boot-starter-jetty",
|
||||||
|
"moduleUrl": "https://spring.io/projects/spring-boot",
|
||||||
|
"moduleVersion": "3.2.4",
|
||||||
|
"moduleLicense": "Apache License, Version 2.0",
|
||||||
|
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "org.springframework.boot:spring-boot-starter-json",
|
"moduleName": "org.springframework.boot:spring-boot-starter-json",
|
||||||
"moduleUrl": "https://spring.io/projects/spring-boot",
|
"moduleUrl": "https://spring.io/projects/spring-boot",
|
||||||
@@ -740,13 +950,6 @@
|
|||||||
"moduleLicense": "Apache License, Version 2.0",
|
"moduleLicense": "Apache License, Version 2.0",
|
||||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"moduleName": "org.springframework.boot:spring-boot-starter-tomcat",
|
|
||||||
"moduleUrl": "https://spring.io/projects/spring-boot",
|
|
||||||
"moduleVersion": "3.2.4",
|
|
||||||
"moduleLicense": "Apache License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "org.springframework.boot:spring-boot-starter-web",
|
"moduleName": "org.springframework.boot:spring-boot-starter-web",
|
||||||
"moduleUrl": "https://spring.io/projects/spring-boot",
|
"moduleUrl": "https://spring.io/projects/spring-boot",
|
||||||
|
|||||||
@@ -8,7 +8,3 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
.duplicate-warning {
|
|
||||||
color: red;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
padding: 0.75rem 3.5rem;
|
padding: 0.75rem 3.5rem;
|
||||||
border: 0.1rem solid var(--md-sys-color-outline-variant);
|
border: 1px solid var(--md-sys-color-outline-variant);
|
||||||
border-radius: 3rem;
|
border-radius: 3rem;
|
||||||
outline-color: var(--md-sys-color-outline-variant);
|
outline-color: var(--md-sys-color-outline-variant);
|
||||||
}
|
}
|
||||||
@@ -19,12 +19,12 @@
|
|||||||
.features-container {
|
.features-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(15rem, 3fr));
|
grid-template-columns: repeat(auto-fill, minmax(15rem, 3fr));
|
||||||
gap: 25px 30px;
|
gap: 30px 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.feature-card {
|
.feature-card {
|
||||||
border: 2px solid var(--md-sys-color-surface-5);
|
border: 1px solid var(--md-sys-color-surface-5);
|
||||||
border-radius: 2rem;
|
border-radius: 1.75rem;
|
||||||
padding: 1.25rem;
|
padding: 1.25rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -47,13 +47,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.feature-card .card-text {
|
.feature-card .card-text {
|
||||||
flex: 1;
|
font-size: .875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.feature-card:hover {
|
.feature-card:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transform: scale(1.1);
|
transform: scale(1.08);
|
||||||
box-shadow: var(--md-sys-elevation-3);
|
box-shadow: var(--md-sys-elevation-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-title.text-primary {
|
.card-title.text-primary {
|
||||||
@@ -79,11 +79,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#tool-text {
|
#tool-text {
|
||||||
margin: 0.5rem 0.5rem 0rem;
|
margin: 0.0rem 0 0 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Only show the favorite icons when the parent card is being hovered over */
|
/* Only show the favorite icons when the parent card is being hovered over */
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon-text {
|
.icon-text {
|
||||||
margin-left: 4px;
|
margin-left: 8px;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -187,8 +187,8 @@ td {
|
|||||||
.feature-card .nav-icon {
|
.feature-card .nav-icon {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-size: 2rem !important;
|
font-size: 2rem !important;
|
||||||
padding: 0.5rem;
|
padding: 0.75rem;
|
||||||
border-radius: 1.25rem;
|
border-radius: 0.9rem;
|
||||||
color: var(--md-sys-color-surface);
|
color: var(--md-sys-color-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -798,6 +798,8 @@ textarea.form-control {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-link {
|
.nav-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
transition: none !important;
|
transition: none !important;
|
||||||
padding: 0.5rem 1rem !important;
|
padding: 0.5rem 1rem !important;
|
||||||
border: 1px transparent;
|
border: 1px transparent;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
.material-symbols-rounded {
|
.material-symbols-rounded {
|
||||||
font-family: 'Material Symbols Rounded';
|
font-family: 'Material Symbols Rounded';
|
||||||
font-weight: normal;
|
font-weight: 300;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
|||||||
@@ -30,5 +30,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.no-fill {
|
.no-fill {
|
||||||
font-variation-settings: var(--md-sys-icon-fill-0);
|
/* font-variation-settings: var(--md-sys-icon-fill-0); */
|
||||||
}
|
}
|
||||||
@@ -47,12 +47,13 @@ function setupFileInput(chooser) {
|
|||||||
const dt = e.dataTransfer;
|
const dt = e.dataTransfer;
|
||||||
const files = dt.files;
|
const files = dt.files;
|
||||||
|
|
||||||
//Do not Update allFiles array here to prevent duplication, the change event listener will take care of that
|
|
||||||
const dataTransfer = new DataTransfer();
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
dataTransfer.items.add(files[i]);
|
allFiles.push(files[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dataTransfer = new DataTransfer();
|
||||||
|
allFiles.forEach((file) => dataTransfer.items.add(file));
|
||||||
|
|
||||||
const fileInput = document.getElementById(elementId);
|
const fileInput = document.getElementById(elementId);
|
||||||
fileInput.files = dataTransfer.files;
|
fileInput.files = dataTransfer.files;
|
||||||
|
|
||||||
@@ -79,40 +80,9 @@ function setupFileInput(chooser) {
|
|||||||
document.body.addEventListener("dragleave", dragleaveListener);
|
document.body.addEventListener("dragleave", dragleaveListener);
|
||||||
document.body.addEventListener("drop", dropListener);
|
document.body.addEventListener("drop", dropListener);
|
||||||
|
|
||||||
// When adding files
|
|
||||||
$("#" + elementId).on("change", function (e) {
|
$("#" + elementId).on("change", function (e) {
|
||||||
// Get newly Added Files
|
allFiles = Array.from(e.target.files);
|
||||||
const newFiles = Array.from(e.target.files).map(file => {
|
|
||||||
return {
|
|
||||||
file: file,
|
|
||||||
uniqueId: file.name + Date.now()// Assign a unique identifier to each file
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add new files to existing files
|
|
||||||
allFiles = [...allFiles, ...newFiles];
|
|
||||||
|
|
||||||
// Update the file input's files property
|
|
||||||
const dataTransfer = new DataTransfer();
|
|
||||||
allFiles.forEach((fileObj) => dataTransfer.items.add(fileObj.file));
|
|
||||||
e.target.files = dataTransfer.files;
|
|
||||||
|
|
||||||
handleFileInputChange(this);
|
handleFileInputChange(this);
|
||||||
|
|
||||||
// Call the displayFiles function with the allFiles array
|
|
||||||
displayFiles(allFiles)
|
|
||||||
// Dispatch a custom event with the allFiles array
|
|
||||||
var filesUpdated = new CustomEvent("filesUpdated", { detail: allFiles });
|
|
||||||
document.dispatchEvent(filesUpdated);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen for event of file being removed and then filter it out of the allFiles array
|
|
||||||
document.addEventListener("fileRemoved", function (e) {
|
|
||||||
const fileId = e.detail;
|
|
||||||
allFiles = allFiles.filter(fileObj => fileObj.uniqueId !== fileId); // Remove the file from the allFiles array using the unique identifier
|
|
||||||
// Dispatch a custom event with the allFiles array
|
|
||||||
var filesUpdated = new CustomEvent("filesUpdated", { detail: allFiles });
|
|
||||||
document.dispatchEvent(filesUpdated);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleFileInputChange(inputElement) {
|
function handleFileInputChange(inputElement) {
|
||||||
@@ -134,4 +104,9 @@ function setupFileInput(chooser) {
|
|||||||
$(inputElement).siblings(".custom-file-label").addClass("selected").html(pdfPrompt);
|
$(inputElement).siblings(".custom-file-label").addClass("selected").html(pdfPrompt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//Listen for event of file being removed and the filter it out of the allFiles array
|
||||||
|
document.addEventListener("fileRemoved", function (e) {
|
||||||
|
const fileName = e.detail;
|
||||||
|
allFiles = allFiles.filter(file => file.name !== fileName);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,21 +61,21 @@ function handleDropdownItemClick(event) {
|
|||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
|
||||||
document.querySelectorAll(".col-lg-2.col-sm-6").forEach((element) => {
|
document.querySelectorAll(".col-lg-2.col-sm-6").forEach((element) => {
|
||||||
const dropdownItems = element.querySelectorAll(".dropdown-item");
|
const dropdownItems = element.querySelectorAll(".dropdown-item");
|
||||||
const items = Array.from(dropdownItems).filter(item => !item.querySelector("hr.dropdown-divider"));
|
const items = Array.from(dropdownItems).filter(item => !item.querySelector("hr.dropdown-divider"));
|
||||||
|
|
||||||
if (items.length <= 2) {
|
if (items.length <= 2) {
|
||||||
if (
|
if (
|
||||||
element.previousElementSibling &&
|
element.previousElementSibling &&
|
||||||
element.previousElementSibling.classList.contains("col-lg-2") &&
|
element.previousElementSibling.classList.contains("col-lg-2") &&
|
||||||
element.previousElementSibling.classList.contains("nav-item-separator")
|
element.previousElementSibling.classList.contains("nav-item-separator")
|
||||||
) {
|
) {
|
||||||
element.previousElementSibling.remove();
|
element.previousElementSibling.remove();
|
||||||
}
|
}
|
||||||
element.remove();
|
element.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//Sort languages by alphabet
|
//Sort languages by alphabet
|
||||||
const list = Array.from(document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').children).filter(
|
const list = Array.from(document.querySelector('.dropdown-menu[aria-labelledby="languageDropdown"]').children).filter(
|
||||||
|
|||||||
@@ -2,27 +2,15 @@ let currentSort = {
|
|||||||
field: null,
|
field: null,
|
||||||
descending: false,
|
descending: false,
|
||||||
};
|
};
|
||||||
//New Array to keep track of unique id
|
|
||||||
let filesWithUniqueId = [];
|
|
||||||
let processedFiles = [];
|
|
||||||
|
|
||||||
document.getElementById("fileInput-input").addEventListener("change", function () {
|
document.getElementById("fileInput-input").addEventListener("change", function () {
|
||||||
var files = Array.from(this.files).map(file => {
|
var files = this.files;
|
||||||
return {
|
|
||||||
file: file,
|
|
||||||
uniqueId: file.name + Date.now()
|
|
||||||
};
|
|
||||||
});
|
|
||||||
filesWithUniqueId = files;
|
|
||||||
displayFiles(files);
|
displayFiles(files);
|
||||||
});
|
});
|
||||||
//Get Files Updated Event from FileInput
|
|
||||||
document.addEventListener("filesUpdated", function (e) {
|
|
||||||
filesWithUniqueId = e.detail;
|
|
||||||
displayFiles(filesWithUniqueId);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {FileList} files
|
||||||
|
*/
|
||||||
function displayFiles(files) {
|
function displayFiles(files) {
|
||||||
const list = document.getElementById("selectedFiles");
|
const list = document.getElementById("selectedFiles");
|
||||||
|
|
||||||
@@ -30,30 +18,12 @@ function displayFiles(files) {
|
|||||||
list.removeChild(list.firstChild);
|
list.removeChild(list.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the processedFiles array
|
|
||||||
processedFiles = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
const item = document.createElement("li");
|
const item = document.createElement("li");
|
||||||
item.className = "list-group-item";
|
item.className = "list-group-item";
|
||||||
item.dataset.id = files[i].uniqueId; // Assign the uniqueId to the list item
|
|
||||||
const fileNameDiv = document.createElement("div");
|
|
||||||
fileNameDiv.className = "filename";
|
|
||||||
fileNameDiv.textContent = files[i].file.name;
|
|
||||||
|
|
||||||
// Check for duplicates and add a warning if necessary
|
|
||||||
const duplicateFiles = files.filter(file => file.file.name === files[i].file.name);
|
|
||||||
if (duplicateFiles.length > 1) {
|
|
||||||
const warning = document.createElement("span");
|
|
||||||
warning.className = "duplicate-warning";
|
|
||||||
warning.textContent = "(Duplicate)";
|
|
||||||
fileNameDiv.appendChild(warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
item.innerHTML = `
|
item.innerHTML = `
|
||||||
<div class="d-flex justify-content-between align-items-center w-100">
|
<div class="d-flex justify-content-between align-items-center w-100">
|
||||||
${fileNameDiv.outerHTML}
|
<div class="filename">${files[i].name}</div>
|
||||||
<div class="arrows d-flex">
|
<div class="arrows d-flex">
|
||||||
<button class="btn btn-secondary move-up"><span>↑</span></button>
|
<button class="btn btn-secondary move-up"><span>↑</span></button>
|
||||||
<button class="btn btn-secondary move-down"><span>↓</span></button>
|
<button class="btn btn-secondary move-down"><span>↓</span></button>
|
||||||
@@ -63,6 +33,7 @@ function displayFiles(files) {
|
|||||||
`;
|
`;
|
||||||
list.appendChild(item);
|
list.appendChild(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
attachMoveButtons();
|
attachMoveButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,18 +66,16 @@ function attachMoveButtons() {
|
|||||||
|
|
||||||
var removeButtons = document.querySelectorAll(".remove-file");
|
var removeButtons = document.querySelectorAll(".remove-file");
|
||||||
for (var i = 0; i < removeButtons.length; i++) {
|
for (var i = 0; i < removeButtons.length; i++) {
|
||||||
// When the delete button is clicked
|
|
||||||
removeButtons[i].addEventListener("click", function (event) {
|
removeButtons[i].addEventListener("click", function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
var parent = this.closest(".list-group-item");
|
var parent = this.closest(".list-group-item");
|
||||||
var fileId = parent.dataset.id; // Get the unique identifier of the file to be deleted
|
//Get name of removed file
|
||||||
|
var fileName = parent.querySelector(".filename").innerText;
|
||||||
parent.remove();
|
parent.remove();
|
||||||
// Remove the file from the filesWithUniqueId array
|
|
||||||
filesWithUniqueId = filesWithUniqueId.filter(fileObj => fileObj.uniqueId !== fileId);
|
|
||||||
updateFiles();
|
updateFiles();
|
||||||
// Dispatch a custom event with the unique identifier of the file to be deleted
|
//Dispatch a custom event with the name of the removed file
|
||||||
var fileRemoved = new CustomEvent("fileRemoved", { detail: fileId });
|
var event = new CustomEvent("fileRemoved", { detail: fileName });
|
||||||
document.dispatchEvent(fileRemoved);
|
document.dispatchEvent(event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,29 +103,30 @@ document.getElementById("sortByDateBtn").addEventListener("click", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function sortFiles(comparator) {
|
function sortFiles(comparator) {
|
||||||
// Sort the filesWithUniqueId array
|
// Convert FileList to array and sort
|
||||||
const sortedFilesArray = filesWithUniqueId.sort((a, b) => comparator(a.file, b.file));
|
const sortedFilesArray = Array.from(document.getElementById("fileInput-input").files).sort(comparator);
|
||||||
|
|
||||||
// Refresh displayed list
|
// Refresh displayed list
|
||||||
displayFiles(sortedFilesArray);
|
displayFiles(sortedFilesArray);
|
||||||
|
|
||||||
// Update the files property
|
// Update the files property
|
||||||
const dataTransfer = new DataTransfer();
|
const dataTransfer = new DataTransfer();
|
||||||
sortedFilesArray.forEach((fileObj) => dataTransfer.items.add(fileObj.file));
|
sortedFilesArray.forEach((file) => dataTransfer.items.add(file));
|
||||||
document.getElementById("fileInput-input").files = dataTransfer.files;
|
document.getElementById("fileInput-input").files = dataTransfer.files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function updateFiles() {
|
function updateFiles() {
|
||||||
var dataTransfer = new DataTransfer();
|
var dataTransfer = new DataTransfer();
|
||||||
var liElements = document.querySelectorAll("#selectedFiles li");
|
var liElements = document.querySelectorAll("#selectedFiles li");
|
||||||
|
const files = document.getElementById("fileInput-input").files;
|
||||||
|
|
||||||
for (var i = 0; i < liElements.length; i++) {
|
for (var i = 0; i < liElements.length; i++) {
|
||||||
var fileIdFromList = liElements[i].dataset.id; // Get the unique identifier from the list item
|
var fileNameFromList = liElements[i].querySelector(".filename").innerText;
|
||||||
for (var j = 0; j < filesWithUniqueId.length; j++) {
|
var fileFromFiles;
|
||||||
var fileObj = filesWithUniqueId[j];
|
for (var j = 0; j < files.length; j++) {
|
||||||
if (fileObj.uniqueId === fileIdFromList) {
|
var file = files[j];
|
||||||
dataTransfer.items.add(fileObj.file);
|
if (file.name === fileNameFromList) {
|
||||||
|
dataTransfer.items.add(file);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ class PdfContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async toRenderer(objectUrl) {
|
async toRenderer(objectUrl) {
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = "pdfjs/pdf.worker.js";
|
pdfjsLib.GlobalWorkerOptions.workerSrc = "./pdfjs/pdf.worker.mjs";
|
||||||
const pdf = await pdfjsLib.getDocument(objectUrl).promise;
|
const pdf = await pdfjsLib.getDocument(objectUrl).promise;
|
||||||
return {
|
return {
|
||||||
document: pdf,
|
document: pdf,
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ document.querySelector("#navbarSearchInput").addEventListener("input", function
|
|||||||
var itemHref = item.getAttribute("href");
|
var itemHref = item.getAttribute("href");
|
||||||
var tags = item.getAttribute("data-bs-tags") || ""; // If no tags, default to empty string
|
var tags = item.getAttribute("data-bs-tags") || ""; // If no tags, default to empty string
|
||||||
|
|
||||||
|
|
||||||
if (titleElement && iconElement && itemHref !== "#") {
|
if (titleElement && iconElement && itemHref !== "#") {
|
||||||
var title = titleElement.innerText;
|
var title = titleElement.innerText;
|
||||||
if (
|
if (
|
||||||
|
|||||||
6581
src/main/resources/static/pdfjs/css/viewer.css
vendored
6581
src/main/resources/static/pdfjs/css/viewer.css
vendored
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user