diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8cc06c75..928b0ec9 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -112,7 +112,7 @@ "ms-python.flake8", // Flake8 linter for Python to enforce code quality "ms-python.python", // Official Microsoft Python extension with IntelliSense, debugging, and Jupyter support "ms-vscode-remote.vscode-remote-extensionpack", // Remote Development Pack for SSH, WSL, and Containers - "Oracle.oracle-java", // Oracle Java extension with additional features for Java development + // "Oracle.oracle-java", // Oracle Java extension with additional features for Java development "streetsidesoftware.code-spell-checker", // Spell checker for code to avoid typos "vmware.vscode-boot-dev-pack", // Developer tools for Spring Boot by VMware "vscjava.vscode-java-pack", // Java Extension Pack with essential Java tools for VS Code diff --git a/.vscode/extensions.json b/.vscode/extensions.json index ab19860d..432bd024 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -6,7 +6,7 @@ "ms-python.flake8", // Flake8 linter for Python to enforce code quality "ms-python.python", // Official Microsoft Python extension with IntelliSense, debugging, and Jupyter support "ms-vscode-remote.vscode-remote-extensionpack", // Remote Development Pack for SSH, WSL, and Containers - "Oracle.oracle-java", // Oracle Java extension with additional features for Java development + // "Oracle.oracle-java", // Oracle Java extension with additional features for Java development "streetsidesoftware.code-spell-checker", // Spell checker for code to avoid typos "vmware.vscode-boot-dev-pack", // Developer tools for Spring Boot by VMware "vscjava.vscode-java-pack", // Java Extension Pack with essential Java tools for VS Code diff --git a/src/main/java/stirling/software/SPDF/EE/EEAppConfig.java b/src/main/java/stirling/software/SPDF/EE/EEAppConfig.java index f1df7d34..a83b1709 100644 --- a/src/main/java/stirling/software/SPDF/EE/EEAppConfig.java +++ b/src/main/java/stirling/software/SPDF/EE/EEAppConfig.java @@ -11,6 +11,7 @@ import stirling.software.SPDF.EE.KeygenLicenseVerifier.License; import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties.EnterpriseEdition; import stirling.software.SPDF.model.ApplicationProperties.Premium; +import stirling.software.SPDF.model.ApplicationProperties.Premium.ProFeatures.GoogleDrive; @Configuration @Order(Ordered.HIGHEST_PRECEDENCE) @@ -43,6 +44,17 @@ public class EEAppConfig { return applicationProperties.getPremium().getProFeatures().isSsoAutoLogin(); } + @Bean(name = "GoogleDriveEnabled") + public boolean googleDriveEnabled() { + return runningProOrHigher() + && applicationProperties.getPremium().getProFeatures().getGoogleDrive().isEnabled(); + } + + @Bean(name = "GoogleDriveConfig") + public GoogleDrive googleDriveConfig() { + return applicationProperties.getPremium().getProFeatures().getGoogleDrive(); + } + // TODO: Remove post migration public void migrateEnterpriseSettingsToPremium(ApplicationProperties applicationProperties) { EnterpriseEdition enterpriseEdition = applicationProperties.getEnterpriseEdition(); diff --git a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java index 6956a28f..6c296083 100644 --- a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java +++ b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java @@ -394,6 +394,7 @@ public class ApplicationProperties { // TODO: Remove post migration @Data + @Deprecated(since = "0.45.0") public static class EnterpriseEdition { private boolean enabled; @ToString.Exclude private String key; @@ -430,6 +431,7 @@ public class ApplicationProperties { public static class ProFeatures { private boolean ssoAutoLogin; private CustomMetadata customMetadata = new CustomMetadata(); + private GoogleDrive googleDrive = new GoogleDrive(); @Data public static class CustomMetadata { @@ -448,6 +450,26 @@ public class ApplicationProperties { : producer; } } + + @Data + public static class GoogleDrive { + private boolean enabled; + private String clientId; + private String apiKey; + private String appId; + + public String getClientId() { + return clientId == null || clientId.trim().isEmpty() ? "" : clientId; + } + + public String getApiKey() { + return apiKey == null || apiKey.trim().isEmpty() ? "" : apiKey; + } + + public String getAppId() { + return appId == null || appId.trim().isEmpty() ? "" : appId; + } + } } @Data diff --git a/src/main/resources/messages_sl_SI.properties b/src/main/resources/messages_sl_SI.properties index 6d687ad0..3e4c2c1d 100644 --- a/src/main/resources/messages_sl_SI.properties +++ b/src/main/resources/messages_sl_SI.properties @@ -234,29 +234,29 @@ adminUserSettings.totalUsers=Skupno število uporabnikov: adminUserSettings.lastRequest=Zadnja zahteva adminUserSettings.usage=View Usage -endpointStatistics.title=Endpoint Statistics -endpointStatistics.header=Endpoint Statistics -endpointStatistics.top10=Top 10 -endpointStatistics.top20=Top 20 -endpointStatistics.all=All -endpointStatistics.refresh=Refresh -endpointStatistics.includeHomepage=Include Homepage ('/') -endpointStatistics.includeLoginPage=Include Login Page ('/login') -endpointStatistics.totalEndpoints=Total Endpoints -endpointStatistics.totalVisits=Total Visits -endpointStatistics.showing=Showing -endpointStatistics.selectedVisits=Selected Visits -endpointStatistics.endpoint=Endpoint -endpointStatistics.visits=Visits -endpointStatistics.percentage=Percentage -endpointStatistics.loading=Loading... -endpointStatistics.failedToLoad=Failed to load endpoint data. Please try refreshing. -endpointStatistics.home=Home -endpointStatistics.login=Login -endpointStatistics.top=Top -endpointStatistics.numberOfVisits=Number of Visits -endpointStatistics.visitsTooltip=Visits: {0} ({1}% of total) -endpointStatistics.retry=Retry +endpointStatistics.title=Statistika končne točke +endpointStatistics.header=Statistika končne točke +endpointStatistics.top10=10 najboljših +endpointStatistics.top20=20 najboljših +endpointStatistics.all=Vse +endpointStatistics.refresh=Osveži +endpointStatistics.includeHomepage=Vključi domačo stran ('/') +endpointStatistics.includeLoginPage=Vključi prijavno stran ('/login') +endpointStatistics.totalEndpoints=Skupno končnih točk +endpointStatistics.totalVisits=Skupno število obiskov +endpointStatistics.showing=Prikaz +endpointStatistics.selectedVisits=Izbrani obiski +endpointStatistics.endpoint=Končna točka +endpointStatistics.visits=Obiski +endpointStatistics.percentage=Odstotek +endpointStatistics.loading=Nalaganje... +endpointStatistics.failedToLoad=Nalaganje podatkov končne točke ni uspelo. Poskusite osvežiti. +endpointStatistics.home=Domača stran +endpointStatistics.login=Prijava +endpointStatistics.top=Na vrh +endpointStatistics.numberOfVisits=Število obiskov +endpointStatistics.visitsTooltip=Obiski: {0} ({1}% vseh) +endpointStatistics.retry=Poskusi znova database.title=Uvoz/izvoz baze podatkov database.header=Uvoz/izvoz baze podatkov @@ -291,14 +291,14 @@ home.viewPdf.title=View/Edit PDF home.viewPdf.desc=Oglejte si, komentirajte, dodajte besedilo ali slike viewPdf.tags=ogled, branje, opomba, besedilo, slika -home.setFavorites=Set Favourites -home.hideFavorites=Hide Favourites -home.showFavorites=Show Favourites -home.legacyHomepage=Old homepage -home.newHomePage=Try our new homepage! -home.alphabetical=Alphabetical -home.globalPopularity=Global Popularity -home.sortBy=Sort by: +home.setFavorites=Nastavi priljubljene +home.hideFavorites=Skrij priljubljene +home.showFavorites=Prikaži priljubljene +home.legacyHomepage=Stara domača stran +home.newHomePage=Preizkusite našo novo domačo stran! +home.alphabetical=Abecedno +home.globalPopularity=Globalna priljubljenost +home.sortBy=Razvrsti po: home.multiTool.title=PDF Multi Tool home.multiTool.desc=Spoji, zavrti, prerazporedi, razdeli in odstrani strani @@ -552,7 +552,7 @@ splitPdfByChapters.tags=razdeli,poglavja,zaznamki,organiziraj home.validateSignature.title=Preveri podpis PDF home.validateSignature.desc=Preveri digitalne podpise in potrdila v dokumentih PDF -validateSignature.tags=podpis,verify,validate,pdf,certificate,digitalni podpis,Validate Signature,Validate certificate +validateSignature.tags=podpis,preveri,validiraj,pdf,certificate,digitalni podpis,Preveri podpis,Preveri certifikat #replace-invert-color replace-color.title=Napredne barvne možnosti @@ -787,7 +787,7 @@ autoSplitPDF.selectText.3=Naložite eno veliko optično prebrano datoteko PDF in autoSplitPDF.selectText.4=Ločilne strani so samodejno zaznane in odstranjene, kar zagotavlja čist končni dokument. autoSplitPDF.formPrompt=Pošljite PDF, ki vsebuje razdelilnike strani Stirling-PDF: autoSplitPDF.duplexMode=Dupleksni način (skeniranje spredaj in zadaj) -autoSplitPDF.dividerDownload2=Prenesi 'Auto Splitter Divider (z navodili).pdf' +autoSplitPDF.dividerDownload2=Prenesi 'Samodejni razdelilnik (z navodili).pdf' autoSplitPDF.submit=Pošlji @@ -886,8 +886,8 @@ sign.last=Zadnja stran sign.next=Naslednja stran sign.previous=Prejšnja stran sign.maintainRatio=Preklopi ohranjanje razmerja stranic -sign.undo=Undo -sign.redo=Redo +sign.undo=Razveljavi +sign.redo=Ponovi #repair repair.title=Popravilo @@ -958,8 +958,8 @@ compress.title=Stisnite compress.header=Stisnite PDF compress.credit=Ta storitev uporablja qpdf za stiskanje/optimizacijo PDF. compress.grayscale.label=Uporabi sivinsko lestvico za stiskanje -compress.selectText.1=Compression Settings -compress.selectText.1.1=1-3 PDF compression,
4-6 lite image compression,
7-9 intense image compression Will dramatically reduce image quality +compress.selectText.1=Nastavitve stiskanja +compress.selectText.1.1=1-3 stiskanje PDF,
4-6 enostavno stiskanje slik,
7-9 intenzivno stiskanje slik Bo dramatično zmanjšalo kakovost slike compress.selectText.2=Raven optimizacije: compress.selectText.4=Samodejni način - Samodejno prilagodi kakovost, da dobi PDF na natančno velikost compress.selectText.5=Pričakovana velikost PDF (npr. 25 MB, 10,8 MB, 25 KB) @@ -1307,15 +1307,15 @@ survey.please=Prosimo, razmislite o sodelovanju v naši anketi, če želite pris survey.disabled=(Pojavno okno ankete bo v naslednjih posodobitvah onemogočeno, vendar na voljo na dnu strani) survey.button=Izpolnite anketo survey.dontShowAgain=Ne prikaži več -survey.meeting.1=If you're using Stirling PDF at work, we'd love to speak to you. We're offering technical support sessions in exchange for a 15 minute user discovery session. -survey.meeting.2=This is a chance to: -survey.meeting.3=Get help with deployment, integrations, or troubleshooting -survey.meeting.4=Provide direct feedback on performance, edge cases, and feature gaps -survey.meeting.5=Help us refine Stirling PDF for real-world enterprise use -survey.meeting.6=If you're interested, you can book time with our team directly. (English speaking only) -survey.meeting.7=Looking forward to digging into your use cases and making Stirling PDF even better! -survey.meeting.notInterested=Not a business and/or interested in a meeting? -survey.meeting.button=Book meeting +survey.meeting.1=Če v službi uporabljate Stirling PDF, bi radi govorili z vami. Ponujamo seje tehnične podpore v zameno za 15-minutno sejo odkrivanja uporabnikov. +survey.meeting.2=To je priložnost za: +survey.meeting.3=Poiščite pomoč pri uvajanju, integracijah ali odpravljanju težav +survey.meeting.4=Zagotovite neposredne povratne informacije o zmogljivosti, robnih primerih in vrzeli v funkcijah +survey.meeting.5=Pomagajte nam izboljšati Stirling PDF za uporabo v podjetju v resničnem svetu +survey.meeting.6=Če ste zainteresirani, si lahko rezervirate čas neposredno pri naši ekipi. (samo angleško govoreči) +survey.meeting.7=Veselimo se poglobitve v vaše primere uporabe in izboljšanja Stirling PDF-ja! +survey.meeting.notInterested=Niste podjetje in/ali vas zanima srečanje? +survey.meeting.button=Rezerviraj srečanje #error error.sorry=Oprostite za težavo! diff --git a/src/main/resources/settings.yml.template b/src/main/resources/settings.yml.template index b7306861..827fec96 100644 --- a/src/main/resources/settings.yml.template +++ b/src/main/resources/settings.yml.template @@ -10,7 +10,6 @@ # If you want to override with environment parameter follow parameter naming SECURITY_INITIALLOGIN_USERNAME # ############################################################################################################# - security: enableLogin: false # set to 'true' to enable login csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production) @@ -61,7 +60,6 @@ security: privateKey: classpath:saml-private-key.key # Your private key. Generated from your keypair spCert: classpath:saml-public-cert.crt # Your signing certificate. Generated from your keypair - premium: key: 00000000-0000-0000-0000-000000000000 enabled: false # Enable license key checks for pro/enterprise features @@ -72,6 +70,11 @@ premium: author: username creator: Stirling-PDF producer: Stirling-PDF + googleDrive: + enabled: false + clientId: '' + apiKey: '' + appId: '' legal: termsAndConditions: https://www.stirlingpdf.com/terms-and-conditions # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder @@ -102,13 +105,11 @@ system: name: postgres # set the name of your database. Should match the name of the database you create customPaths: pipeline: - watchedFoldersDir: "" #Defaults to /pipeline/watchedFolders - finishedFoldersDir: "" #Defaults to /pipeline/finishedFolders + watchedFoldersDir: '' #Defaults to /pipeline/watchedFolders + finishedFoldersDir: '' #Defaults to /pipeline/finishedFolders operations: - weasyprint: "" #Defaults to /opt/venv/bin/weasyprint - unoconvert: "" #Defaults to /opt/venv/bin/unoconvert - - + weasyprint: '' #Defaults to /opt/venv/bin/weasyprint + unoconvert: '' #Defaults to /opt/venv/bin/unoconvert ui: appName: '' # application's visible name @@ -130,7 +131,7 @@ AutomaticallyGenerated: appVersion: 0.35.0 processExecutor: - sessionLimit: # Process executor instances limits + sessionLimit: # Process executor instances limits libreOfficeSessionLimit: 1 pdfToHtmlSessionLimit: 1 qpdfSessionLimit: 4 @@ -139,7 +140,7 @@ processExecutor: weasyPrintSessionLimit: 16 installAppSessionLimit: 1 calibreSessionLimit: 1 - timeoutMinutes: # Process executor timeout in minutes + timeoutMinutes: # Process executor timeout in minutes libreOfficetimeoutMinutes: 30 pdfToHtmltimeoutMinutes: 20 pythonOpenCvtimeoutMinutes: 30 diff --git a/src/main/resources/static/css/fileSelect.css b/src/main/resources/static/css/fileSelect.css index afb0b075..88bef91d 100644 --- a/src/main/resources/static/css/fileSelect.css +++ b/src/main/resources/static/css/fileSelect.css @@ -271,3 +271,28 @@ align-items: center; z-index: 9999; } + +.google-drive-button { + width: 2.5rem; + pointer-events: auto; + cursor: pointer; + transition-duration: 0.4s; + border-radius: 0.5rem; + box-shadow: 0 0 5px var(--md-sys-color-on-surface-variant); + background-color: var(--md-sys-color-on-surface-container-high) +} + +.horizontal-divider { + width: 85%; + border-top: 1px dashed; + padding: 0px; + margin: 10px; +} + +.google-drive-button img { + width:100% +} + +.google-drive-button:hover { + background-color: var(--md-sys-color-on-surface-variant) +} diff --git a/src/main/resources/static/images/google-drive.svg b/src/main/resources/static/images/google-drive.svg new file mode 100644 index 00000000..03b2f212 --- /dev/null +++ b/src/main/resources/static/images/google-drive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/static/js/fileInput.js b/src/main/resources/static/js/fileInput.js index 28331ef0..32922390 100644 --- a/src/main/resources/static/js/fileInput.js +++ b/src/main/resources/static/js/fileInput.js @@ -35,6 +35,7 @@ function setupFileInput(chooser) { const pdfPrompt = chooser.getAttribute('data-bs-pdf-prompt'); const inputContainerId = chooser.getAttribute('data-bs-element-container-id'); const showUploads = chooser.getAttribute('data-bs-show-uploads') === "true"; + const name = chooser.getAttribute('data-bs-unique-id') const noFileSelectedPrompt = chooser.getAttribute('data-bs-no-file-selected'); let inputContainer = document.getElementById(inputContainerId); @@ -87,6 +88,21 @@ function setupFileInput(chooser) { overlay = false; } + const googleDriveFileListener = function (e) { + const googleDriveFiles = e.detail; + + const fileInput = document.getElementById(elementId); + if (fileInput?.hasAttribute('multiple')) { + pushFileListTo(googleDriveFiles, allFiles); + } else if (fileInput) { + allFiles = [googleDriveFiles[0]]; + } + + const dataTransfer = new DataTransfer(); + allFiles.forEach((file) => dataTransfer.items.add(file)); + fileInput.files = dataTransfer.files; + fileInput.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { source: 'drag-drop' } })); + } const dropListener = function (e) { e.preventDefault(); @@ -137,6 +153,7 @@ function setupFileInput(chooser) { document.body.addEventListener('dragenter', dragenterListener); document.body.addEventListener('dragleave', dragleaveListener); document.body.addEventListener('drop', dropListener); + document.body.addEventListener(name + 'GoogleDriveDrivePicked', googleDriveFileListener); $('#' + elementId).on('change', async function (e) { let element = e.target; diff --git a/src/main/resources/static/js/googleFilePicker.js b/src/main/resources/static/js/googleFilePicker.js new file mode 100644 index 00000000..20516788 --- /dev/null +++ b/src/main/resources/static/js/googleFilePicker.js @@ -0,0 +1,158 @@ +const SCOPES = "https://www.googleapis.com/auth/drive.readonly"; +const SESSION_STORAGE_ID = "googleDrivePickerAccessToken"; + +let tokenClient; +let accessToken = sessionStorage.getItem(SESSION_STORAGE_ID); + +let isScriptExecuted = false; +if (!isScriptExecuted) { + isScriptExecuted = true; + document.addEventListener("DOMContentLoaded", function () { + document.querySelectorAll(".google-drive-button").forEach(setupGoogleDrivePicker); + }); +} + +function gisLoaded() { + tokenClient = google.accounts.oauth2.initTokenClient({ + client_id: window.stirlingPDF.GoogleDriveClientId, + scope: SCOPES, + callback: "", // defined later + }); +} + +// add more as needed. +// Google picker is limited on what mimeTypes are supported +// Wild card are not supported +const expandableMimeTypes = { + "image/*" : ["image/jpeg", "image/png","image/svg+xml" ] +} + +function fileInputToGooglePickerMimeTypes(accept) { + + if(accept == null || accept == "" || accept.includes("*/*")){ + + // Setting null will accept all supported mimetypes + return null; + } + + let mimeTypes = []; + accept.split(',').forEach(part => { + if(!(part in expandableMimeTypes)){ + mimeTypes.push(part); + return; + } + + expandableMimeTypes[part].forEach(mimeType => { + mimeTypes.push(mimeType); + }); + }); + + const mimeString = mimeTypes.join(",").replace(/\s+/g, ''); + console.log([accept, "became", mimeString]); + return mimeString; +} + +/** + * Callback after api.js is loaded. + */ +function gapiLoaded() { + gapi.load("client:picker", initializePicker); +} + +/** + * Callback after the API client is loaded. Loads the + * discovery doc to initialize the API. + */ +async function initializePicker() { + await gapi.client.load("https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"); +} + +function setupGoogleDrivePicker(picker) { + + const name = picker.getAttribute('data-name'); + const accept = picker.getAttribute('data-accept'); + const multiple = picker.getAttribute('data-multiple') === "true"; + const mimeTypes = fileInputToGooglePickerMimeTypes(accept); + + picker.addEventListener("click", onGoogleDriveButtonClick); + + function onGoogleDriveButtonClick(e) { + e.stopPropagation(); + + tokenClient.callback = (response) => { + if (response.error !== undefined) { + throw response; + } + accessToken = response.access_token; + sessionStorage.setItem(SESSION_STORAGE_ID, accessToken); + createGooglePicker(); + }; + + tokenClient.requestAccessToken({ prompt: accessToken === null ? "consent" : "" }); + } + + /** + * Sign out the user upon button click. + */ + function signOut() { + if (accessToken) { + sessionStorage.removeItem(SESSION_STORAGE_ID); + google.accounts.oauth2.revoke(accessToken); + accessToken = null; + } + } + + function createGooglePicker() { + let builder = new google.picker.PickerBuilder() + .setDeveloperKey(window.stirlingPDF.GoogleDriveApiKey) + .setAppId(window.stirlingPDF.GoogleDriveAppId) + .setOAuthToken(accessToken) + .addView( + new google.picker.DocsView() + .setIncludeFolders(true) + .setMimeTypes(mimeTypes) + ) + .addView( + new google.picker.DocsView() + .setIncludeFolders(true) + .setEnableDrives(true) + .setMimeTypes(mimeTypes) + ) + .setCallback(pickerCallback); + + if(multiple) { + builder.enableFeature(google.picker.Feature.MULTISELECT_ENABLED); + } + const picker = builder.build(); + + picker.setVisible(true); + } + + /** + * Displays the file details of the user's selection. + * @param {object} data - Containers the user selection from the picker + */ + async function pickerCallback(data) { + if (data.action === google.picker.Action.PICKED) { + const files = await Promise.all( + data[google.picker.Response.DOCUMENTS].map(async (pickedFile) => { + const fileId = pickedFile[google.picker.Document.ID]; + console.log(fileId); + const res = await gapi.client.drive.files.get({ + fileId: fileId, + alt: "media", + }); + + let file = new File([new Uint8Array(res.body.length).map((_, i) => res.body.charCodeAt(i))], pickedFile.name, { + type: pickedFile.mimeType, + lastModified: pickedFile.lastModified, + endings: pickedFile.endings, + }); + return file; + }) + ); + + document.body.dispatchEvent(new CustomEvent(name+"GoogleDriveDrivePicked", { detail: files })); + } + } +} diff --git a/src/main/resources/templates/fragments/common.html b/src/main/resources/templates/fragments/common.html index a407f60e..f42b013b 100644 --- a/src/main/resources/templates/fragments/common.html +++ b/src/main/resources/templates/fragments/common.html @@ -228,8 +228,9 @@ loading: '[[#{loading}]]' };
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ google drive
@@ -258,4 +265,16 @@
+ +
+ + + + + +
\ No newline at end of file