diff --git a/.github/workflows/lint-ruby.yml b/.github/workflows/lint-ruby.yml index 4b476edf5..12e5cff20 100644 --- a/.github/workflows/lint-ruby.yml +++ b/.github/workflows/lint-ruby.yml @@ -30,7 +30,7 @@ jobs: - name: Install Dependencies run: | - bundle update + bundle install - name: Rubocop run: | diff --git a/.gitmodules b/.gitmodules index 6dd796ec1..d7f56c93b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -48,10 +48,6 @@ path = web/documentserver-example/java/src/main/resources/assets/document-formats url = https://github.com/ONLYOFFICE/document-formats branch = master -[submodule "web/documentserver-example/java-spring/src/main/resources/assets/document-formats"] - path = web/documentserver-example/java-spring/src/main/resources/assets/document-formats - url = https://github.com/ONLYOFFICE/document-formats - branch = master [submodule "web/documentserver-example/csharp/assets/document-templates"] path = web/documentserver-example/csharp/assets/document-templates url = https://github.com/ONLYOFFICE/document-templates diff --git a/3rd-Party.license b/3rd-Party.license index d762967ce..2dd945f6c 100644 --- a/3rd-Party.license +++ b/3rd-Party.license @@ -1,6 +1,5 @@ Document Server integration example uses code from the following 3rd party projects. - web/documentserver-example/csharp jQuery.BlockUI - The jQuery BlockUI Plugin lets you simulate synchronous behavior when using AJAX, without locking the browser. (https://github.com/malsup/blockui/) @@ -132,6 +131,10 @@ Jackson Databind - General-purpose data-binding functionality and tree-model for License: Apache 2.0 License File: jackson-databind.license +Jackson Dataformat Properties - Support for reading and writing content of "Java Properties" style configuration files as if there was implied nesting structure (by default using dots as separators). (https://github.com/FasterXML/jackson-dataformats-text/blob/master/LICENSE) +License: Apache 2.0 +License File: jackson-dataformat-properties.license + jQuery.BlockUI - The jQuery BlockUI Plugin lets you simulate synchronous behavior when using AJAX, without locking the browser. (https://github.com/malsup/blockui/) License: MIT, GPL License File: jQuery.BlockUI.license @@ -156,6 +159,10 @@ jQuery.UI - jQuery UI is an open source library of interface components — License: MIT License File: jQuery.UI.license +JSON - JSON is a light-weight, language independent, data interchange format. (https://github.com/stleary/JSON-java/blob/master/LICENSE) +License Public Domain +License File: json.license + JSON.simple - JSON.simple is a simple Java toolkit for JSON. You can use JSON.simple to encode or decode JSON text. (https://github.com/fangyidong/json-simple/blob/master/LICENSE.txt) License: Apache 2.0 License File: JSON.simple.license @@ -168,11 +175,11 @@ ModelMapper - ModelMapper is an intelligent object mapping library that automa License: Apache 2.0 License File modelmapper.license -Prime JWT - is intended to be fast and easy to use. Prime JWT has a single external dependency on Jackson. (https://github.com/ws-apps/prime-jwt/blob/master/LICENSE) +Spring Boot - Helps create Spring-powered, production-grade applications and services. Has external dependencies on Spring Framework. (https://github.com/spring-projects/spring-boot/blob/main/LICENSE.txt) License: Apache 2.0 -License File: prime-jwt.license +License File: spring-boot.license -Spring Boot - Helps create Spring-powered, production-grade applications and services. Has external dependencies on Spring Framework. (https://github.com/spring-projects/spring-boot/blob/main/LICENSE.txt) +Spring Boot Web - Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container. (https://github.com/spring-projects/spring-boot/blob/main/LICENSE.txt) License: Apache 2.0 License File: spring-boot.license @@ -271,6 +278,9 @@ urllib - Request HTTP URLs in a complex world — basic and digest authen License: MIT License File: urllib.license +utf7 - Encodes and decodes JavaScript (Unicode/UCS-2) strings to UTF-7 ASCII strings. (https://github.com/kkaefer/utf7/blob/master/LICENSE) +License: MIT +License File: utf7.license web/documentserver-example/php diff --git a/CHANGELOG.md b/CHANGELOG.md index 50a1eb67b..8b98e10c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Change Log +## 1.10.0 +- nodejs: converting function on index page +- java-spring: using java docs-integration-sdk +- tabs menu +- creating and editing pdf instead docxf +- filling by default +- forgotten files +- delete all files +- save as for pdf +- handling conversion -9 error +- change inserted image +- different goback for users + ## 1.9.0 - nodejs: filling by default - nodejs: docxf, oform as pdf documentType diff --git a/Readme.md b/Readme.md index 680f1e265..2e13f8e09 100644 --- a/Readme.md +++ b/Readme.md @@ -1,11 +1,11 @@ ## Integration examples -Test examples are simple document management systems that can be built into your application for testing. +These test examples are simple document management systems that can be built into your application for testing. Do NOT use these integration examples on your own server without proper code modifications! In case you enabled any of the test examples, disable it before going for production. These examples show the way to integrate [ONLYOFFICE Docs][2] into your own website or application using one of the programming languages. -The package contains examples written in .Net (C# MVC), .Net (C#), Java, Node.js, PHP and Ruby. +The package contains examples written in .Net (C# MVC), .Net (C#), Java, Java Spring, Node.js, PHP, Python and Ruby. You should change `http://documentserver` to your server address in these files: * [.Net (C# MVC)](https://github.com/ONLYOFFICE/document-server-integration/tree/master/web/documentserver-example/csharp-mvc) - `web/documentserver-example/csharp-mvc/web.appsettings.config` @@ -13,9 +13,9 @@ You should change `http://documentserver` to your server address in these files: * [Java](https://github.com/ONLYOFFICE/document-server-integration/tree/master/web/documentserver-example/java) - `web/documentserver-example/java/src/main/resources/settings.properties` * [Java Spring](https://github.com/ONLYOFFICE/document-server-integration/tree/master/web/documentserver-example/java-spring) - `web/documentserver-example/java-spring/src/main/resources/application.properties` * [Node.js](https://github.com/ONLYOFFICE/document-server-integration/tree/master/web/documentserver-example/nodejs) - `web/documentserver-example/nodejs/config/default.json` -* [PHP](https://github.com/ONLYOFFICE/document-server-integration/tree/master/web/documentserver-example/php) - `web/documentserver-example/php/config.json` -* [Python](https://github.com/ONLYOFFICE/document-server-integration/tree/master/web/documentserver-example/python) - `web/documentserver-example/python/config.py` -* [Ruby](https://github.com/ONLYOFFICE/document-server-integration/tree/master/web/documentserver-example/ruby) - `web/documentserver-example/ruby/config/application.rb` +* [PHP](https://github.com/ONLYOFFICE/document-server-integration/tree/master/web/documentserver-example/php) - `web/documentserver-example/php/src/configuration/ConfigurationManager.php` +* [Python](https://github.com/ONLYOFFICE/document-server-integration/tree/master/web/documentserver-example/python) - `web/documentserver-example/python/src/configuration/configuration.py` +* [Ruby](https://github.com/ONLYOFFICE/document-server-integration/tree/master/web/documentserver-example/ruby) - `web/documentserver-example/ruby/app/configuration/configuration.rb` More information on how to use these examples can be found here: [http://api.onlyoffice.com/editors/demopreview](http://api.onlyoffice.com/editors/demopreview "http://api.onlyoffice.com/editors/demopreview") diff --git a/web/documentserver-example/csharp-mvc/App_Start/BundleConfig.cs b/web/documentserver-example/csharp-mvc/App_Start/BundleConfig.cs index 0613f38ba..659039ce5 100644 --- a/web/documentserver-example/csharp-mvc/App_Start/BundleConfig.cs +++ b/web/documentserver-example/csharp-mvc/App_Start/BundleConfig.cs @@ -38,9 +38,15 @@ public static void RegisterBundles(BundleCollection bundles) // create the main script bundle bundles.Add(new ScriptBundle("~/bundles/scripts").Include( + "~/Scripts/formats.js", "~/Scripts/jscript.js" )); + // create the forgotten page script bundle + bundles.Add(new ScriptBundle("~/bundles/forgotten").Include( + "~/Scripts/forgotten.js" + )); + // create a style bundle bundles.Add(new StyleBundle("~/Content/css").Include( "~/Content/stylesheet.css", @@ -52,6 +58,11 @@ public static void RegisterBundles(BundleCollection bundles) bundles.Add(new StyleBundle("~/Content/editor").Include( "~/Content/editor.css" )); + + // create the forgotten page style bundle + bundles.Add(new StyleBundle("~/Content/forgotten").Include( + "~/Content/forgotten.css" + )); } } } \ No newline at end of file diff --git a/web/documentserver-example/csharp-mvc/Content/forgotten.css b/web/documentserver-example/csharp-mvc/Content/forgotten.css new file mode 100644 index 000000000..4256b01b0 --- /dev/null +++ b/web/documentserver-example/csharp-mvc/Content/forgotten.css @@ -0,0 +1,161 @@ +.center { + width: auto; +} + +.left-panel { + width: 256px; +} + +.main-panel { + width: 832px; + margin: 0 32px; + padding: 48px 0; + left: 0; +} + +.tableRow { + display: flex; + width: 100%; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #E2E2E2; +} + +.tableRow td:first-child { + width: 70%; + flex-grow: 0; + max-width: none; +} + +.tableHeader td:first-child { + text-align: left; +} + +.tableHeader td:last-child, .tableRow td:last-child { + width: 10%; + text-align: center; + padding: 0 !important; +} + +.tableHeader { + width: 100%; +} + +.stored-edit { + display: block; + padding-top: 0; + max-width: none; +} + +menu.links { + width: 100%; +} + +.scroll-table-body table { + table-layout: fixed; +} + +.stored-edit span { + font-size: 12px; + line-height: normal; + position: static; +} + +.scroll-table-body { + overflow-y: auto; +} + +.stored-list { + height: calc(100% - 58px); +} + +header { + min-width:auto; +} + +header a { + display: block; + margin: 0 auto; + width: 1152px; +} + +@media (max-width: 1279px) and (min-width: 1024px) { + .left-panel { + width: 208px; + } + + .main-panel { + width: 688px; + } + + header a { + width: 928px; + } + + header img { + margin-left: 16px; + } +} + +@media (max-width: 1023px) and (min-width: 593px) { + .center { + max-width: 768px; + width: calc(100% - 80px); + } + + .table-main { + width: 100%; + } + + .left-panel { + width: 208px; + } + + .main-panel { + width: calc(100% - 32px); + } + + .tableHeader td:last-child, .tableRow td:last-child { + width: 20%; + } + + header a { + width: 768px; + } + + header img { + margin-left: 40px; + } +} + +@media (max-width: 592px) and (min-width: 320px) { + .center, .table-main { + width: 100%; + } + + .left-panel { + display: none; + } + + .main-panel { + width: 100%; + margin: 0; + padding: 28px 16px; + } + + .tableHeader td:last-child, .tableRow td:last-child { + width: 25%; + } + + header a { + width: auto; + } + + .scroll-table-body { + top: 40px; + } + + .tableRow { + padding: 8px 0; + } +} \ No newline at end of file diff --git a/web/documentserver-example/csharp-mvc/Content/images/file_docxf.svg b/web/documentserver-example/csharp-mvc/Content/images/file_docxf.svg deleted file mode 100644 index ab5f02686..000000000 --- a/web/documentserver-example/csharp-mvc/Content/images/file_docxf.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/documentserver-example/csharp-mvc/Content/images/file_pdf.svg b/web/documentserver-example/csharp-mvc/Content/images/file_pdf.svg new file mode 100644 index 000000000..33694e18d --- /dev/null +++ b/web/documentserver-example/csharp-mvc/Content/images/file_pdf.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/documentserver-example/csharp-mvc/Content/images/home.svg b/web/documentserver-example/csharp-mvc/Content/images/home.svg new file mode 100644 index 000000000..e67b12994 --- /dev/null +++ b/web/documentserver-example/csharp-mvc/Content/images/home.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/web/documentserver-example/csharp-mvc/Content/images/icon_pdf.svg b/web/documentserver-example/csharp-mvc/Content/images/icon_pdf.svg new file mode 100644 index 000000000..1867081aa --- /dev/null +++ b/web/documentserver-example/csharp-mvc/Content/images/icon_pdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/documentserver-example/csharp-mvc/Content/images/logo.png b/web/documentserver-example/csharp-mvc/Content/images/logo.png deleted file mode 100644 index d433d89e2..000000000 Binary files a/web/documentserver-example/csharp-mvc/Content/images/logo.png and /dev/null differ diff --git a/web/documentserver-example/csharp-mvc/Content/images/mobile-logo.svg b/web/documentserver-example/csharp-mvc/Content/images/mobile-logo.svg new file mode 100644 index 000000000..e8b8cb5d7 --- /dev/null +++ b/web/documentserver-example/csharp-mvc/Content/images/mobile-logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/documentserver-example/csharp-mvc/Content/images/mobile-menu.svg b/web/documentserver-example/csharp-mvc/Content/images/mobile-menu.svg new file mode 100644 index 000000000..b40f11580 --- /dev/null +++ b/web/documentserver-example/csharp-mvc/Content/images/mobile-menu.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/documentserver-example/csharp-mvc/Content/images/plus.svg b/web/documentserver-example/csharp-mvc/Content/images/plus.svg new file mode 100644 index 000000000..673a1f23d --- /dev/null +++ b/web/documentserver-example/csharp-mvc/Content/images/plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/documentserver-example/csharp-mvc/Content/media.css b/web/documentserver-example/csharp-mvc/Content/media.css index 705085cc6..93baf3395 100644 --- a/web/documentserver-example/csharp-mvc/Content/media.css +++ b/web/documentserver-example/csharp-mvc/Content/media.css @@ -79,7 +79,8 @@ margin-left: 0; } - .tableRow { + .tableRow, + menu.links { width: 90%; } @@ -119,7 +120,8 @@ .contentCells-icon{ width: 5%; } - .tableRow { + .tableRow, + menu.links { width: 55%; } @@ -175,7 +177,8 @@ } @media (max-width: 715px) { - .tableRow { + .tableRow, + menu.links { width: 45%; } } @@ -263,7 +266,8 @@ padding-left: 0; } - .tableRow { + .tableRow, + menu.links { width: 75%; } @@ -293,7 +297,8 @@ width: 580px; } - .tableRow { + .tableRow, + menu.links { width: 95%; } @@ -407,7 +412,8 @@ padding: 16px 0 6px; } - .tableRow { + .tableRow, + menu.links { width: 40%; } @@ -502,6 +508,17 @@ justify-content: space-between; align-items: center; } + .buttonsMobile.indent { + margin-bottom: 0; + flex-wrap: nowrap; + } + .button.file-type:hover, + .button.file-type { + height: 28px; + width: 100px; + margin-bottom: 10px !important; + font-size: 9px; + } .button.gray{ margin: 0; } @@ -527,7 +544,8 @@ } @media (max-width: 510px) and (min-width: 470px) { - .tableRow { + .tableRow, + menu.links { width: 35%; } @@ -553,7 +571,8 @@ } @media (max-width: 470px) and (min-width: 420px) { - .tableRow { + .tableRow, + menu.links { width: 30%; } .tableRow td:first-child{ @@ -588,7 +607,8 @@ } @media (max-width: 420px) and (min-width: 320px) { - .tableRow { + .tableRow, + menu.links { width: 25%; } @@ -638,7 +658,8 @@ } } @media (max-width: 769px) and (min-width: 715px){ - .tableRow{ + .tableRow, + menu.links { width: 50%; } } @@ -687,3 +708,216 @@ max-width: none; } } + +@media (max-width: 592px) and (min-width: 320px) { + body.menu-open { + overflow: hidden; + } + + header { + min-width: auto; + height: fit-content; + } + + header a { + display: block; + } + + header img { + margin: 0; + } + + header, footer { + position: -webkit-sticky; /* Safari */ + position: sticky; + top: 0; + z-index: 100; + } + + .center { + width: 100%; + margin: 0; + } + + .left-panel { + background-color: rgba(186, 186, 186, 0.6); + display: none; + flex-direction: row; + align-items: start; + max-width: none; + width: 100%; + margin: 0; + position: fixed; + left: 0; + height: calc(100% - 124px); + z-index:99; + } + + .left-panel.active { + display: flex; + } + + .help-block { + height: 100%; + margin: 0; + background-color: #F5F5F5; + width: 248px; + padding-left: 16px; + padding-top: 33px; + padding-bottom: 33px; + padding-right: 40px; + box-sizing: border-box; + overflow-y: auto; + overflow-x: hidden; + } + + .table-main { + width: 100%; + } + + .mobile-close-btn { + display: block; + width: 48px; + height: 48px; + background-color: #E2E2E2; + border-radius: 2px; + border-color: #E2E2E2; + color: #808080; + cursor: pointer; + outline: inherit; + border: none; + } + + .main-panel { + width: 100%; + left: 0; + padding: 28px 16px; + } + + #portal-info { + width: 100%; + max-width: fit-content; + } + + menu.links { + width: 100%; + margin-top: 0; + padding: 0; + } + + span.portal-name { + font-size: 16px; + } + + span.portal-descr:first-child { + font-size: 13px; + } + + span.portal-descr { + font-size: 12px; + } + + .user-descr { + width: 100%; + max-width: none; + min-width: auto; + border-bottom: 1px solid #E5E5E5; + padding: 12px 0; + margin: 0; + cursor: pointer; + } + + .user-descr ul { + display: none; + } + + .user-descr ul.active { + display: block; + } + + .user-descr b { + font-size: 13px; + display: flex; + align-items: center; + column-gap: 8px; + margin: 0; + } + + .user-descr b::before { + content: url("images/plus.svg"); + display: inline-block; + width: 24px; + height: 24px; + } + + .storedHeader { + width: 100%; + } + + .storedHeaderClearAll { + padding-right: 0; + } + + .scroll-table-body { + top: 36px; + } + + .scroll-table-body tr:first-child { + padding-top: 0; + } + + .tableRow { + border-bottom: 1px solid #e5e5e5; + padding: 16px 0; + width: 100%; + } + + .tableRow td:first-child { + width: 100%; + } + + .stored-edit span { + font-size: 14px; + } + + .header-list { + font-size: 16px; + } + + .firstContentCellViewers { + border-bottom: none !important; + } + + .firstContentCellViewers ~ td { + border-bottom: none !important; + } + + .downloadContentCellShift:after { + display: none; + } + + .main-nav { + display: none; + } + + .responsive-nav { + height: 44px; + display: flex; + flex-direction: row; + margin: 0; + align-items: center; + column-gap: 16px; + padding: 10px 16px; + width: 100%; + box-sizing: border-box; + list-style: none; + } + + .main { + height: calc(100% - 124px); + } + + .user-block-table { + height: auto; + } +} diff --git a/web/documentserver-example/csharp-mvc/Content/stylesheet.css b/web/documentserver-example/csharp-mvc/Content/stylesheet.css index 6ca4a7718..2de064dac 100644 --- a/web/documentserver-example/csharp-mvc/Content/stylesheet.css +++ b/web/documentserver-example/csharp-mvc/Content/stylesheet.css @@ -65,6 +65,14 @@ header img { margin: 10px 0 22px 32px; } +.responsive-nav { + display: none; +} + +.mobile-close-btn { + display: none; +} + .center { position:relative; margin: 0 auto 0; @@ -164,7 +172,7 @@ label .checkbox { } .try-editor.form { - background-image: url("images/file_docxf.svg"); + background-image: url("images/file_pdf.svg"); } .side-option { @@ -230,6 +238,33 @@ label .checkbox { color: #FF6F3D; } +.button.file-type { + font-size: 11px; + color: #FFFFFF; + padding: 8px 8px; + margin-right: 10px; +} + +.button.file-type.disable { + cursor: default; +} + +.button.file-type.pale { + opacity: 30%; +} + +.button.file-type.document { + background: #446995; +} + +.button.file-type.spreadsheet { + background: #40865C; +} + +.button.file-type.presentation { + background: #AA5252; +} + .upload-panel { float: left; padding: 24px 0; @@ -271,6 +306,43 @@ label .checkbox { border-bottom: 1px solid #D0D5DA; } +.links { + display: flex; + padding: 0; + column-gap: 30px; + align-items: center; + list-style: none; + border-bottom: 1px solid #E2E2E2; + margin: 0; + margin-bottom: 24px; +} + +.links li { + padding: 4px; + border-bottom: 2px solid transparent; + margin-bottom: -1px; +} + +.links li.active { + border-bottom: 2px solid #FF6F3D; +} + +.links li.active a { + color: #FF6F3D; +} + +.links li.active a img { + filter: invert(55%) sepia(67%) saturate(2727%) hue-rotate(335deg) brightness(104%) contrast(101%); +} + +.links a { + display: inline-block; + padding: 2px 0; + line-height: 20px; + font-size: 13px; + text-decoration: none; +} + #mainProgress { color: #333333; display: none; @@ -464,6 +536,11 @@ footer table tr td:first-child { background-image: url("images/icon_pptx.svg"); } +.stored-edit.pdf, +.uploadFileName.pdf { + background-image: url("images/icon_pdf.svg"); +} + .stored-edit span { font-size: 12px; line-height: 12px; @@ -592,6 +669,29 @@ footer table tr td:first-child { width: 4%; } +.storedHeader { + display: flex; + width: 100%; + justify-content: space-between; + align-items: center; +} + +.storedHeaderClearAll { + padding-right: 52px; +} + +.clear-all { + display: inline-block; + width: 100px; + padding: 2px; + outline: 1px solid #E5E5E5; + text-align: center; + cursor:pointer; + text-transform: uppercase; + background-color: #F5F5F5; + color: #666666; +} + .select-user { color: #444444; font-family: Open Sans; @@ -741,6 +841,16 @@ html { margin-left: 25px; } +.buttonsMobile.indent{ + padding-left: 35px; + margin-top: 10px; + margin-bottom: 10px; +} + +.invisible { + display: none; +} + .tooltip { background: #FFFFFF; border-radius: 5px; diff --git a/web/documentserver-example/csharp-mvc/Controllers/HomeController.cs b/web/documentserver-example/csharp-mvc/Controllers/HomeController.cs index c863de2ea..eb39e5655 100644 --- a/web/documentserver-example/csharp-mvc/Controllers/HomeController.cs +++ b/web/documentserver-example/csharp-mvc/Controllers/HomeController.cs @@ -17,10 +17,13 @@ */ using System; +using System.Collections.Generic; +using System.Collections; using System.IO; using System.Web.Mvc; using OnlineEditorsExampleMVC.Helpers; using OnlineEditorsExampleMVC.Models; +using System.Web.Configuration; namespace OnlineEditorsExampleMVC.Controllers { @@ -31,6 +34,46 @@ public ActionResult Index() return View(); } + public ActionResult Forgotten() + { + if (!bool.Parse(WebConfigurationManager.AppSettings["enable-forgotten"])) + { + ViewData["Message"] = "The forgotten page is disabled"; + return View("~/Views/Shared/Error.aspx"); + } + + var files = new List>(); + + try + { + var response = TrackManager.commandRequest("getForgottenList", null); + ArrayList keys = (ArrayList)response["keys"]; + + // fetch all the forgotten files from the document server + foreach (string key in keys) + { + var file = new Dictionary(); + var fileResult = TrackManager.commandRequest("getForgotten", key); + file.Add("key", fileResult["key"].ToString()); + file.Add("url", fileResult["url"].ToString()); + file.Add( + "type", + FileUtility.GetFileType(fileResult["url"].ToString()) + .ToString() + .ToLower() + ); + + files.Add(file); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(ex.Message); + } + + return View("Forgotten", new ForgottenFilesModel(files)); + } + // viewing file in the editor public ActionResult Editor(string fileName, string editorsMode, string editorsType, string directUrl) { @@ -52,7 +95,7 @@ public ActionResult Sample(string fileExt, bool? sample) var id = Request.Cookies.GetOrDefault("uid", null); var user = Users.getUser(id); DocManagerHelper.CreateMeta(fileName, user.id, user.name); // create meta information for the sample document - Response.Redirect(Url.Action("Editor", "Home", new { fileName = fileName })); + Response.Redirect(Url.Action("Editor", "Home", new { fileName = fileName, editorsMode="edit" })); return null; } } diff --git a/web/documentserver-example/csharp-mvc/Helpers/DocumentConverter.cs b/web/documentserver-example/csharp-mvc/Helpers/DocumentConverter.cs index 3f395ec3a..0ee440192 100644 --- a/web/documentserver-example/csharp-mvc/Helpers/DocumentConverter.cs +++ b/web/documentserver-example/csharp-mvc/Helpers/DocumentConverter.cs @@ -229,6 +229,10 @@ private static void ProcessResponseError(int errorCode) switch (errorCode) { + case -9: + // public const int c_nErrorConversionOutputFormatError = -9; + errorMessage = String.Format(errorMessageTemplate, "Error conversion output format"); + break; case -8: // public const int c_nErrorFileVKey = -8; errorMessage = String.Format(errorMessageTemplate, "Error document VKey"); diff --git a/web/documentserver-example/csharp-mvc/Helpers/TrackManager.cs b/web/documentserver-example/csharp-mvc/Helpers/TrackManager.cs index 27ff3f007..79261415e 100644 --- a/web/documentserver-example/csharp-mvc/Helpers/TrackManager.cs +++ b/web/documentserver-example/csharp-mvc/Helpers/TrackManager.cs @@ -282,7 +282,7 @@ public static int processForceSave(Dictionary fileData, string f } // create a command request - public static void commandRequest(string method, string key, object meta = null) + public static Dictionary commandRequest(string method, string key, object meta = null) { DocManagerHelper.VerifySSL(); @@ -345,6 +345,7 @@ public static void commandRequest(string method, string key, object meta = null) { throw new Exception(dataResponse); } + return responseObj; } // save file diff --git a/web/documentserver-example/csharp-mvc/Helpers/Users.cs b/web/documentserver-example/csharp-mvc/Helpers/Users.cs index 60469aa96..402cfc40c 100644 --- a/web/documentserver-example/csharp-mvc/Helpers/Users.cs +++ b/web/documentserver-example/csharp-mvc/Helpers/Users.cs @@ -59,7 +59,9 @@ public class Users "Can’t print the file", "Can create new files from the editor", "Can see the information about Group2 users", - "Can’t submit forms" + "Can’t submit forms", + "Can't close history", + "Can't restore the file version" }; static List descr_user_0 = new List() @@ -92,7 +94,8 @@ public class Users new List(), descr_user_1, true, - true + true, + new Goback(null, false) ), new User( "uid-2", @@ -111,7 +114,8 @@ public class Users new List(), descr_user_2, false, - true + true, + new Goback("Go to Documents", null) ), new User( "uid-3", @@ -130,7 +134,8 @@ public class Users new List() { "copy", "download", "print" }, descr_user_3, false, - false + false, + null ), new User( "uid-0", @@ -144,7 +149,8 @@ public class Users new List() { "protect" }, descr_user_0, false, - false + false, + null ) }; @@ -235,7 +241,9 @@ public class User public List userInfoGroups; public bool avatar; - public User(string id, string name, string email, string group, List reviewGroups, Dictionary commentGroups, List userInfoGroups, bool? favorite, List deniedPermissions, List descriptions, bool templates, bool avatar) + public Goback goback; + + public User(string id, string name, string email, string group, List reviewGroups, Dictionary commentGroups, List userInfoGroups, bool? favorite, List deniedPermissions, List descriptions, bool templates, bool avatar, Goback goback) { this.id = id; this.name = name; @@ -249,6 +257,21 @@ public User(string id, string name, string email, string group, List rev this.templates = templates; this.userInfoGroups = userInfoGroups; this.avatar = avatar; + this.goback = goback; + } + } + + public class Goback + { + public string text; + public bool? blank; + + public Goback(){} + + public Goback(string text, bool? blank) + { + this.text = text; + this.blank = blank; } } } diff --git a/web/documentserver-example/csharp-mvc/Models/FileModel.cs b/web/documentserver-example/csharp-mvc/Models/FileModel.cs index e77f4b86f..291474737 100755 --- a/web/documentserver-example/csharp-mvc/Models/FileModel.cs +++ b/web/documentserver-example/csharp-mvc/Models/FileModel.cs @@ -77,18 +77,19 @@ public string GetDocConfig(HttpRequest request, UrlHelper url) var jss = new JavaScriptSerializer(); var ext = Path.GetExtension(FileName).ToLower(); // get file extension - var editorsMode = Mode ?? "edit"; // get editor mode + var canFill = DocManagerHelper.FillFormExts.Contains(ext); + var editorsMode = Mode ?? (canFill ? "fillForms" : "edit"); // get editor mode var canEdit = DocManagerHelper.EditedExts.Contains(ext); // check if the file with such an extension can be edited var id = request.Cookies.GetOrDefault("uid", null); var user = Users.getUser(id); // get the user - if ((!canEdit && editorsMode.Equals("edit") || editorsMode.Equals("fillForms")) && DocManagerHelper.FillFormExts.Contains(ext)) { + if ((!canEdit && editorsMode.Equals("edit") || editorsMode.Equals("fillForms")) && canFill) { editorsMode = "fillForms"; canEdit = true; } - var submitForm = editorsMode.Equals("fillForms") && id.Equals("uid-1"); // check if the Submit form button is displayed or not + var submitForm = (editorsMode.Equals("fillForms") || editorsMode.Equals("embedded")) && user.id.Equals("uid-1"); // check if the Submit form button is displayed or not var mode = canEdit && editorsMode != "view" ? "edit" : "view"; // set the mode parameter: change it to view if the document can't be edited // favorite icon state @@ -214,10 +215,12 @@ public string GetDocConfig(HttpRequest request, UrlHelper url) { "forcesave", false }, // adds the request for the forced file saving to the callback handler { "submitForm", submitForm }, // if the Submit form button is displayed or not { - "goback", new Dictionary // settings for the Open file location menu button and upper right corner button + "goback", user.goback != null ? new Dictionary // settings for the Open file location menu button and upper right corner button { - { "url", DocManagerHelper.GetServerUrl(false) } // the absolute URL to the website address which will be opened when clicking the Open file location menu button - } + { "url", DocManagerHelper.GetServerUrl(false) }, // the absolute URL to the website address which will be opened when clicking the Open file location menu button + { "text", user.goback.text }, + { "blank", user.goback.blank } + } : new Dictionary{} } } } @@ -289,20 +292,20 @@ public void GetLogoConfig(out string logoUrl) { Path = HttpRuntime.AppDomainAppVirtualPath + (HttpRuntime.AppDomainAppVirtualPath.EndsWith("/") ? "" : "/") - + "Content\\images\\logo.png" + + "Content\\images\\logo.svg" }; var directMailMergeUrl = new UriBuilder(DocManagerHelper.GetServerUrl(false)) { Path = HttpRuntime.AppDomainAppVirtualPath + (HttpRuntime.AppDomainAppVirtualPath.EndsWith("/") ? "" : "/") - + "Content\\images\\logo.png" + + "Content\\images\\logo.svg" }; // create a logo config var logoConfig = new Dictionary { - { "fileType", "png"}, + { "fileType", "svg"}, { "url", mailMergeUrl.ToString()} }; diff --git a/web/documentserver-example/csharp-mvc/Models/FileUtility.cs b/web/documentserver-example/csharp-mvc/Models/FileUtility.cs index 99b2357de..e55797fb3 100644 --- a/web/documentserver-example/csharp-mvc/Models/FileUtility.cs +++ b/web/documentserver-example/csharp-mvc/Models/FileUtility.cs @@ -33,7 +33,8 @@ public enum FileType { Word, Cell, - Slide + Slide, + Pdf } // get file type @@ -41,6 +42,7 @@ public static FileType GetFileType(string fileName) { var ext = Path.GetExtension(fileName).ToLower(); + if (FormatManager.PdfExtensions().Contains(ext)) return FileType.Pdf; // pdf type for document extensions if (FormatManager.DocumentExtensions().Contains(ext)) return FileType.Word; // word type for document extensions if (FormatManager.SpreadsheetExtensions().Contains(ext)) return FileType.Cell; // cell type for spreadsheet extensions if (FormatManager.PresentationExtensions().Contains(ext)) return FileType.Slide; // slide type for presentation extensions @@ -185,6 +187,20 @@ public static List Documents() .ToList(); } + public static List PdfExtensions() + { + return Pdfs() + .Select(format => format.Extension()) + .ToList(); + } + + public static List Pdfs() + { + return All() + .Where(format => format.Type == FileType.Pdf) + .ToList(); + } + public static List AllExtensions() { return All() diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/dto/ChangesHistory.java b/web/documentserver-example/csharp-mvc/Models/ForgottenFilesModel.cs old mode 100644 new mode 100755 similarity index 65% rename from web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/dto/ChangesHistory.java rename to web/documentserver-example/csharp-mvc/Models/ForgottenFilesModel.cs index 28bec4c64..04a927f9f --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/dto/ChangesHistory.java +++ b/web/documentserver-example/csharp-mvc/Models/ForgottenFilesModel.cs @@ -1,4 +1,4 @@ -/** +/** * * (c) Copyright Ascensio System SIA 2024 * @@ -16,16 +16,15 @@ * */ -package com.onlyoffice.integration.dto; +using System.Collections.Generic; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; +namespace OnlineEditorsExampleMVC.Models +{ + // create file model + public class ForgottenFilesModel + { + public List> files { get; set; } -@Data -@AllArgsConstructor -@NoArgsConstructor -public class ChangesHistory { - private String created; - private ChangesUser user; -} + public ForgottenFilesModel(List> files) { this.files = files; } + } +} \ No newline at end of file diff --git a/web/documentserver-example/csharp-mvc/OnlineEditorsExampleMVC.csproj b/web/documentserver-example/csharp-mvc/OnlineEditorsExampleMVC.csproj index e9e884332..9c1fe77b1 100644 --- a/web/documentserver-example/csharp-mvc/OnlineEditorsExampleMVC.csproj +++ b/web/documentserver-example/csharp-mvc/OnlineEditorsExampleMVC.csproj @@ -116,6 +116,7 @@ + WebEditor.ashx @@ -123,6 +124,7 @@ + @@ -140,18 +142,20 @@ + - + + @@ -159,6 +163,10 @@ + + + + @@ -166,17 +174,13 @@ + Designer - - - - - diff --git a/web/documentserver-example/csharp-mvc/Scripts/forgotten.js b/web/documentserver-example/csharp-mvc/Scripts/forgotten.js new file mode 100755 index 000000000..056583795 --- /dev/null +++ b/web/documentserver-example/csharp-mvc/Scripts/forgotten.js @@ -0,0 +1,19 @@ +function deleteFile(event) { + let filename = event.currentTarget.getAttribute("data"); + filename = encodeURIComponent(filename); + let url = `webeditor.ashx?type=removeforgotten&filename=${filename}`; + + fetch(url, { + headers: { + "Content-Type": "application/json", + } + }).then(result => { + if(result.status == 204) { + document.location.reload(true); + } + }); +} + +document.querySelectorAll('.delete-file').forEach(el => { + el.addEventListener('click', deleteFile); +}); \ No newline at end of file diff --git a/web/documentserver-example/csharp-mvc/Scripts/formats.js b/web/documentserver-example/csharp-mvc/Scripts/formats.js new file mode 100644 index 000000000..e423eeb20 --- /dev/null +++ b/web/documentserver-example/csharp-mvc/Scripts/formats.js @@ -0,0 +1,66 @@ +/** + * + * (c) Copyright Ascensio System SIA 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +class Format { + constructor(name, type, actions, convert, mime) { + this.name = name; + this.type = type; + this.actions = actions; + this.convert = convert; + this.mime = mime; + } + + isAutoConvertible() { + return this.actions.includes('auto-convert'); + } + + isEditable() { + return this.actions.includes('edit') || this.actions.includes('lossy-edit'); + } + + isFillable() { + return this.actions.includes('fill'); + } +} + +class FormatManager { + formats = []; + + constructor(formats) { + if(Array.isArray(formats)) this.formats = formats; + } + + findByExtension(extension) { + return this.formats.find(format => format.name == extension); + } + + isAutoConvertible(extension) { + let format = this.findByExtension(extension); + return format !== undefined && format.isAutoConvertible(); + } + + isEditable(extension) { + let format = this.findByExtension(extension); + return format !== undefined && format.isEditable(); + } + + isFillable(extension) { + let format = this.findByExtension(extension); + return format !== undefined && format.isFillable(); + } +} \ No newline at end of file diff --git a/web/documentserver-example/csharp-mvc/Scripts/jscript.js b/web/documentserver-example/csharp-mvc/Scripts/jscript.js index 35d827964..3adcd21f4 100644 --- a/web/documentserver-example/csharp-mvc/Scripts/jscript.js +++ b/web/documentserver-example/csharp-mvc/Scripts/jscript.js @@ -17,6 +17,27 @@ */ var directUrl; +var formatManager; + +window.onload = function () { + fetch("webeditor.ashx?type=formats") + .then((response) => response.json()) + .then((data) => { + if (data.formats) { + let formats = []; + data.formats.forEach(format => { + formats.push(new Format( + format.Name, + format.Type, + format.Actions, + format.Convert, + format.Mime + )); + }); + formatManager = new FormatManager(formats); + } + }) +} if (typeof jQuery != "undefined") { jq = jQuery.noConflict(); @@ -87,8 +108,8 @@ if (typeof jQuery != "undefined") { }); var timer = null; - var checkConvert = function (filePass) { - filePass = filePass ? filePass : null; + var checkConvert = function (filePass, fileType) { + filePass = filePass ? filePass : null; if (timer != null) { clearTimeout(timer); } @@ -100,10 +121,10 @@ if (typeof jQuery != "undefined") { jq("#filePass").val(""); var fileName = jq("#hiddenFileName").val(); - var posExt = fileName.lastIndexOf('.'); + var posExt = fileName.lastIndexOf('.') + 1; posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : ''; - if (ConverExtList.indexOf(posExt) == -1) { + if (!formatManager.isAutoConvertible(posExt)) { jq("#step2").addClass("done").removeClass("current"); loadScripts(); return; @@ -116,7 +137,7 @@ if (typeof jQuery != "undefined") { contentType: "text/xml", type: "post", dataType: "json", - data: JSON.stringify({ filename: fileName, filePass: filePass }), + data: JSON.stringify({ filename: fileName, filePass: filePass, fileExt: fileType }), url: UrlConverter, complete: function (data) { var responseText = data.responseText; @@ -132,6 +153,12 @@ if (typeof jQuery != "undefined") { } return; } else { + if (response.error.includes("Error conversion output format")) { + jq("#select-file-type").removeClass("invisible"); + jq("#step2").removeClass("current"); + jq("#hiddenFileName").attr("placeholder", filePass); + return; + } jq(".current").removeClass("current"); jq(".step:not(.done)").addClass("error"); jq("#mainProgress .error-message").show().find("span").text(response.error); @@ -143,7 +170,7 @@ if (typeof jQuery != "undefined") { jq("#hiddenFileName").val(response.filename); if (response.step && response.step < 100) { - checkConvert(filePass); + checkConvert(filePass, fileType); } else { jq("#step2").addClass("done").removeClass("current"); loadScripts(); @@ -178,10 +205,10 @@ if (typeof jQuery != "undefined") { jq("#beginView, #beginEmbedded").removeClass("disable"); var fileName = jq("#hiddenFileName").val(); - var posExt = fileName.lastIndexOf('.'); + var posExt = fileName.lastIndexOf('.') + 1; posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : ''; - if (EditedExtList.indexOf(posExt) != -1 || FillExtList.indexOf(posExt) != -1) { + if (formatManager.isEditable(posExt) || formatManager.isFillable(posExt)) { jq("#beginEdit").removeClass("disable"); } }; @@ -213,6 +240,15 @@ if (typeof jQuery != "undefined") { }); }; + jq(document).on("click", ".file-type:not(.disable)", function () { + const currentElement = jq(this); + var fileType = currentElement.attr("data"); + var filePass = jq("#hiddenFileName").attr("placeholder"); + jq('.file-type').addClass(["disable", "pale"]); + currentElement.removeClass("pale"); + checkConvert(filePass, fileType); + }); + jq(document).on("click", "#enterPass", function () { var filePass = jq("#filePass").val(); if (filePass) { @@ -294,6 +330,23 @@ if (typeof jQuery != "undefined") { }); }); + jq(document).on("click", ".clear-all", function () { + if (confirm("Delete all the files?")) { + var requestAddress = "webeditor.ashx" + + "?type=remove"; + jq.ajax({ + async: true, + contentType: "text/xml", + url: requestAddress, + complete: function (data) { + if (JSON.parse(data.responseText).success) { + window.location.reload(true); + } + } + }); + } + }); + function showUserTooltip (isMobile) { if ( jq("div#portal-info").is(":hidden") ) { jq("div#portal-info").show(); @@ -368,4 +421,27 @@ if (typeof jQuery != "undefined") { }).mouseout(function () { jq("div.tooltip").remove(); }); -} \ No newline at end of file +} + +function toggleSidePanel(event) { + event.preventDefault(); + let sidePanel = document.querySelector(".left-panel"); + let body = document.querySelector("body"); + if (sidePanel.classList.contains("active")) { + sidePanel.classList.remove("active"); + body.classList.remove("menu-open"); + } else { + sidePanel.classList.add("active") + body.classList.add("menu-open"); + } +} + +function toggleUserDescr(event) { + let list = event.currentTarget.querySelector("ul"); + let cursor = window.getComputedStyle(event.currentTarget).getPropertyValue("cursor"); + + if (cursor === "pointer") { + if (list.classList.contains("active")) list.classList.remove("active"); + else list.classList.add("active"); + } +} diff --git a/web/documentserver-example/csharp-mvc/Views/Home/Editor.aspx b/web/documentserver-example/csharp-mvc/Views/Home/Editor.aspx index c66888a8d..5468103bf 100644 --- a/web/documentserver-example/csharp-mvc/Views/Home/Editor.aspx +++ b/web/documentserver-example/csharp-mvc/Views/Home/Editor.aspx @@ -108,7 +108,7 @@ // the meta information of the document is changed via the meta command var onMetaChange = function (event) { - if (event.data.favorite) { + if (event.data.favorite !== undefined) { var favorite = !!event.data.favorite; var title = document.title.replace(/^\☆/g, ""); document.title = (favorite ? "☆" : "") + title; @@ -259,6 +259,48 @@ } } + <% string usersForMentions; %> + <% Model.GetUsersMentions(Request, out usersForMentions); %> + <% string usersInfo; %> + <% Model.GetUsersInfo(Request, out usersInfo); %> + <% string usersForProtect; %> + <% Model.GetUsersProtect(Request, out usersForProtect); %> + + var onRequestUsers = function (event) { + if (event && event.data){ + var c = event.data.c; + } + switch (c) { + case "info": + users = []; + var allUsers = <%= usersInfo %>; + for (var i = 0; i < event.data.id.length; i++) { + for (var j = 0; j < allUsers.length; j++) { + if (allUsers[j].id == event.data.id[i]) { + users.push(allUsers[j]); + break; + } + } + } + break; + case "protect": + var users = <%= usersForProtect %>; + break; + default: + users = <%= usersForMentions %>; + } + docEditor.setUsers({ + "c": c, + "users": users, + }); + }; + + var onRequestSendNotify = function (event) { + event.data.actionLink = replaceActionLink(location.href, JSON.stringify(event.data.actionLink)); + var data = JSON.stringify(event.data); + innerAlert("onRequestSendNotify: " + data); + }; + config = <%= Model.GetDocConfig(Request, Url) %>; config.width = "100%"; @@ -276,63 +318,28 @@ "onRequestSelectSpreadsheet": onRequestSelectSpreadsheet, }; - <% string usersForMentions; %> - <% Model.GetUsersMentions(Request, out usersForMentions); %> - <% string usersInfo; %> - <% Model.GetUsersInfo(Request, out usersInfo); %> - <% string usersForProtect; %> - <% Model.GetUsersProtect(Request, out usersForProtect); %> - if (config.editorConfig.user.id) { // the user is trying to show the document version history config.events['onRequestHistory'] = onRequestHistory; // the user is trying to click the specific document version in the document version history config.events['onRequestHistoryData'] = onRequestHistoryData; // the user is trying to go back to the document from viewing the document version history - config.events['onRequestHistoryClose'] = function () { - document.location.reload(); - }; - config.events['onRequestRestore'] = onRequestRestore; + if (config.editorConfig.user.id !== "uid-3") { + config.events['onRequestHistoryClose'] = function () { + document.location.reload(); + }; + config.events['onRequestRestore'] = onRequestRestore; + } // add mentions for not anonymous users <% if (!string.IsNullOrEmpty(usersForMentions)) { %> - config.events['onRequestUsers'] = function (event) { - if (event && event.data){ - var c = event.data.c; - } - switch (c) { - case "info": - users = []; - var allUsers = <%= usersInfo %>; - for (var i = 0; i < event.data.id.length; i++) { - for (var j = 0; j < allUsers.length; j++) { - if (allUsers[j].id == event.data.id[i]) { - users.push(allUsers[j]); - break; - } - } - } - break; - case "protect": - var users = <%= usersForProtect %>; - break; - default: - users = <%= usersForMentions %>; - } - docEditor.setUsers({ - "c": c, - "users": users, - }); - }; + config.events['onRequestUsers'] = onRequestUsers; <% } %> + config.events['onRequestSaveAs'] = onRequestSaveAs; // the user is mentioned in a comment - config.events['onRequestSendNotify'] = function (event) { - event.data.actionLink = replaceActionLink(location.href, JSON.stringify(event.data.actionLink)); - var data = JSON.stringify(event.data); - innerAlert("onRequestSendNotify: " + data); - }; + config.events['onRequestSendNotify'] = onRequestSendNotify; // prevent file renaming for anonymous users config.events['onRequestRename'] = onRequestRename; config.events['onRequestReferenceData'] = onRequestReferenceData; @@ -341,17 +348,7 @@ config.events['onRequestOpen'] = onRequestOpen; } - if (config.editorConfig.createUrl) { - config.events.onRequestSaveAs = onRequestSaveAs; - }; - var сonnectEditor = function () { - if ((config.document.fileType === "docxf" || config.document.fileType === "oform") - && DocsAPI.DocEditor.version().split(".")[0] < 7) { - innerAlert("Please update ONLYOFFICE Docs to version 7.0 to work on fillable forms online."); - return; - } - docEditor = new DocsAPI.DocEditor("iframeEditor", config); }; diff --git a/web/documentserver-example/csharp-mvc/Views/Home/Forgotten.aspx b/web/documentserver-example/csharp-mvc/Views/Home/Forgotten.aspx new file mode 100755 index 000000000..8e1d1670f --- /dev/null +++ b/web/documentserver-example/csharp-mvc/Views/Home/Forgotten.aspx @@ -0,0 +1,144 @@ +<%@ Page Title="ONLYOFFICE" Language="C#" Inherits="System.Web.Mvc.ViewPage" %> + +<%@ Import Namespace="System.IO" %> +<%@ Import Namespace="System.Web.Configuration" %> +<%@ Import Namespace="OnlineEditorsExampleMVC.Helpers" %> + + + + + + + + + /> + + ONLYOFFICE + + " rel="shortcut icon" type="image/x-icon" /> + + + + <%: Styles.Render("~/Content/css") %> + <%: Styles.Render("~/Content/forgotten") %> + + +
+ + +
  • + + ONLYOFFICE + +
  • +
  • + + ONLYOFFICE + +
  • +
    +
    + +
    + + + + + + + +
    +
    + + +
  • + Forgotten files +
  • +
    +
    +
    +
    + Forgotten files +
    +
    + + + + + + + +
    FilenameAction
    +
    + + + <% foreach (var file in Model.files) { %> + "> + + + + <% } %> + +
    + " href="<%= file["url"] %>" target="_blank"> + <%= file["key"] %> + + + "> + Download + "> + Delete +
    +
    +
    +
    +
    +
    + + + + <%: Scripts.Render("~/bundles/jquery", "~/bundles/forgotten") %> + + diff --git a/web/documentserver-example/csharp-mvc/Views/Home/Index.aspx b/web/documentserver-example/csharp-mvc/Views/Home/Index.aspx index 080f867ff..421f7c2cb 100644 --- a/web/documentserver-example/csharp-mvc/Views/Home/Index.aspx +++ b/web/documentserver-example/csharp-mvc/Views/Home/Index.aspx @@ -41,11 +41,23 @@
    -
    - + + +
  • + + ONLYOFFICE + +
  • +
  • + + ONLYOFFICE + +
  • +
    @@ -68,7 +80,7 @@ Presentation
  • - PDF form + PDF form
  • +
    + + + <% if (bool.Parse(WebConfigurationManager.AppSettings["enable-forgotten"])) { %> +
  • + Forgotten files +
  • + <% } %> +
    <% var storedFiles = DocManagerHelper.GetStoredFiles(); %>
    "> ONLYOFFICE Document Editors – Welcome! @@ -136,7 +163,7 @@ You can open the same document using different users in different Web browser sessions, so you can check out multi-user editing functions. <% foreach (User user in Users.getAllUsers()) { %> -
    +
    <%= user.name.IsEmpty() ? "Anonymous" : user.name %>
      <% foreach (string description in user.descriptions) @@ -151,7 +178,14 @@ if (storedFiles.Any()) { %>
      - Your documents +
      +
      + Your documents +
      +
      +
      Clear all
      +
      +
      @@ -193,11 +227,13 @@ Open in editor for mobile devices - + <% if (docType != "pdf") { %> + + <% } %> <% if (docType == "word") { %>
      - " target="_blank"> - Open in editor for comment - - + " target="_blank"> + Open in editor for comment + + " target="_blank"> @@ -295,6 +331,15 @@
      After these steps are completed, you can work with your document.
      1. Loading the file. The loading speed depends on file size and additional elements it contains. +
      2. Conversion. The file is converted to OOXML so that you can edit it. @@ -357,9 +402,6 @@ <%: Scripts.Render("~/bundles/jquery", "~/bundles/scripts") %> diff --git a/web/documentserver-example/csharp-mvc/Views/Shared/Error.aspx b/web/documentserver-example/csharp-mvc/Views/Shared/Error.aspx index 6b0cdb5c6..7b07b291a 100644 --- a/web/documentserver-example/csharp-mvc/Views/Shared/Error.aspx +++ b/web/documentserver-example/csharp-mvc/Views/Shared/Error.aspx @@ -26,6 +26,7 @@

      Error.

      An error occurred while processing your request.

      +

      <%= ViewData["Message"] %>

      \ No newline at end of file diff --git a/web/documentserver-example/csharp-mvc/WebEditor.ashx.cs b/web/documentserver-example/csharp-mvc/WebEditor.ashx.cs index 4e69b85c1..79183f3e2 100644 --- a/web/documentserver-example/csharp-mvc/WebEditor.ashx.cs +++ b/web/documentserver-example/csharp-mvc/WebEditor.ashx.cs @@ -69,6 +69,9 @@ public void ProcessRequest(HttpContext context) case "remove": Remove(context); break; + case "removeforgotten": + RemoveForgotten(context); + break; case "assets": Assets(context); break; @@ -87,6 +90,9 @@ public void ProcessRequest(HttpContext context) case "reference": Reference(context); break; + case "formats": + Formats(context); + break; } } @@ -240,7 +246,13 @@ private static void Convert(HttpContext context) var fileUri = DocManagerHelper.GetDownloadUrl(fileName); var extension = (Path.GetExtension(fileName).ToLower() ?? "").Trim('.'); - var internalExtension = "ooxml"; + string conversionExtension = "ooxml"; + object fileExt; + + if (body.TryGetValue("fileExt", out fileExt) && !String.IsNullOrEmpty(fileExt.ToString())) + { + conversionExtension = fileExt.ToString(); + } // check if the file with such an extension can be converted if (DocManagerHelper.ConvertExts.Contains("." + extension)) @@ -258,7 +270,7 @@ private static void Convert(HttpContext context) // get the url and file type of the converted file Dictionary newFileData; - var result = ServiceConverter.GetConvertedData(downloadUri.ToString(), extension, internalExtension, key, true, out newFileData, filePass, lang); + var result = ServiceConverter.GetConvertedData(downloadUri.ToString(), extension, conversionExtension, key, true, out newFileData, filePass, lang); if (result != 100) { context.Response.Write("{ \"step\" : \"" + result + "\", \"filename\" : \"" + fileName + "\"}"); @@ -393,8 +405,17 @@ private static void Remove(HttpContext context) context.Response.ContentType = "text/plain"; try { - var fileName = Path.GetFileName(context.Request["fileName"]); - Remove(fileName); // remove a file and its history if it exists + string fileName = context.Request["fileName"]; + + if (!String.IsNullOrEmpty(fileName)) + { + fileName = Path.GetFileName(context.Request["fileName"]); + Remove(fileName); // remove a file and its history if it exists + } + else + { + RemoveUserDirectory(); // remove the user's directory + } context.Response.Write("{ \"success\": true }"); } @@ -414,6 +435,14 @@ private static void Remove(string fileName) if (Directory.Exists(histDir)) Directory.Delete(histDir, true); } + // remove the user's directory + private static void RemoveUserDirectory() + { + var path = DocManagerHelper.StoragePath("", null); // get the path to the user directory + + if (Directory.Exists(path)) Directory.Delete(path, true); + } + // get files information private static void Files(HttpContext context) { @@ -951,6 +980,51 @@ private static Dictionary GetHistory(string fileName) return history; } + // return all the supported formats + private static void Formats(HttpContext context) + { + try + { + Dictionary data = new Dictionary + { + { "formats", FormatManager.All() } + }; + context.Response.ContentType = "application/json"; + var jss = new JavaScriptSerializer(); + + context.Response.Write(jss.Serialize(data)); + } + catch (Exception e) + { + context.Response.Write("{ \"error\": \"" + e.Message + "\"}"); + } + } + + // delete a forgotten file from the document server + private static void RemoveForgotten(HttpContext context) + { + try + { + if (!bool.Parse(WebConfigurationManager.AppSettings["enable-forgotten"])) + { + throw new HttpException(403, "The forgotten page is disabled"); + } + + string filename = context.Request["filename"]; + + if (!String.IsNullOrEmpty(filename)) + { + TrackManager.commandRequest("deleteForgotten", filename); + } + + context.Response.StatusCode = 204; + } + catch (Exception e) + { + context.Response.StatusCode = 500; + context.Response.Write("{ \"error\": \"" + e.Message + "\"}"); + } + } } } \ No newline at end of file diff --git a/web/documentserver-example/csharp-mvc/assets/document-formats b/web/documentserver-example/csharp-mvc/assets/document-formats index 2c3e099f1..dbd7ce514 160000 --- a/web/documentserver-example/csharp-mvc/assets/document-formats +++ b/web/documentserver-example/csharp-mvc/assets/document-formats @@ -1 +1 @@ -Subproject commit 2c3e099f1e8eaaf7070cdb81643de9586a2235e2 +Subproject commit dbd7ce514589be824ba9c3a4793c00937a35947e diff --git a/web/documentserver-example/csharp-mvc/assets/document-templates b/web/documentserver-example/csharp-mvc/assets/document-templates index 1fc823afa..c9fc1ee6b 160000 --- a/web/documentserver-example/csharp-mvc/assets/document-templates +++ b/web/documentserver-example/csharp-mvc/assets/document-templates @@ -1 +1 @@ -Subproject commit 1fc823afa909e4c49551e4e5b945189a21ff1999 +Subproject commit c9fc1ee6beac118a7d9472f971a61cd2eb285293 diff --git a/web/documentserver-example/csharp-mvc/web.appsettings.config b/web/documentserver-example/csharp-mvc/web.appsettings.config index 7a8a9ca1f..7d16f7153 100644 --- a/web/documentserver-example/csharp-mvc/web.appsettings.config +++ b/web/documentserver-example/csharp-mvc/web.appsettings.config @@ -1,11 +1,12 @@ - + + diff --git a/web/documentserver-example/csharp/App_Themes/forgotten.css b/web/documentserver-example/csharp/App_Themes/forgotten.css new file mode 100755 index 000000000..4256b01b0 --- /dev/null +++ b/web/documentserver-example/csharp/App_Themes/forgotten.css @@ -0,0 +1,161 @@ +.center { + width: auto; +} + +.left-panel { + width: 256px; +} + +.main-panel { + width: 832px; + margin: 0 32px; + padding: 48px 0; + left: 0; +} + +.tableRow { + display: flex; + width: 100%; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #E2E2E2; +} + +.tableRow td:first-child { + width: 70%; + flex-grow: 0; + max-width: none; +} + +.tableHeader td:first-child { + text-align: left; +} + +.tableHeader td:last-child, .tableRow td:last-child { + width: 10%; + text-align: center; + padding: 0 !important; +} + +.tableHeader { + width: 100%; +} + +.stored-edit { + display: block; + padding-top: 0; + max-width: none; +} + +menu.links { + width: 100%; +} + +.scroll-table-body table { + table-layout: fixed; +} + +.stored-edit span { + font-size: 12px; + line-height: normal; + position: static; +} + +.scroll-table-body { + overflow-y: auto; +} + +.stored-list { + height: calc(100% - 58px); +} + +header { + min-width:auto; +} + +header a { + display: block; + margin: 0 auto; + width: 1152px; +} + +@media (max-width: 1279px) and (min-width: 1024px) { + .left-panel { + width: 208px; + } + + .main-panel { + width: 688px; + } + + header a { + width: 928px; + } + + header img { + margin-left: 16px; + } +} + +@media (max-width: 1023px) and (min-width: 593px) { + .center { + max-width: 768px; + width: calc(100% - 80px); + } + + .table-main { + width: 100%; + } + + .left-panel { + width: 208px; + } + + .main-panel { + width: calc(100% - 32px); + } + + .tableHeader td:last-child, .tableRow td:last-child { + width: 20%; + } + + header a { + width: 768px; + } + + header img { + margin-left: 40px; + } +} + +@media (max-width: 592px) and (min-width: 320px) { + .center, .table-main { + width: 100%; + } + + .left-panel { + display: none; + } + + .main-panel { + width: 100%; + margin: 0; + padding: 28px 16px; + } + + .tableHeader td:last-child, .tableRow td:last-child { + width: 25%; + } + + header a { + width: auto; + } + + .scroll-table-body { + top: 40px; + } + + .tableRow { + padding: 8px 0; + } +} \ No newline at end of file diff --git a/web/documentserver-example/csharp/App_Themes/images/file_docxf.svg b/web/documentserver-example/csharp/App_Themes/images/file_docxf.svg deleted file mode 100644 index ab5f02686..000000000 --- a/web/documentserver-example/csharp/App_Themes/images/file_docxf.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/documentserver-example/csharp/App_Themes/images/file_pdf.svg b/web/documentserver-example/csharp/App_Themes/images/file_pdf.svg new file mode 100644 index 000000000..33694e18d --- /dev/null +++ b/web/documentserver-example/csharp/App_Themes/images/file_pdf.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/documentserver-example/csharp/App_Themes/images/home.svg b/web/documentserver-example/csharp/App_Themes/images/home.svg new file mode 100644 index 000000000..e67b12994 --- /dev/null +++ b/web/documentserver-example/csharp/App_Themes/images/home.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/web/documentserver-example/csharp/App_Themes/images/icon_pdf.svg b/web/documentserver-example/csharp/App_Themes/images/icon_pdf.svg new file mode 100644 index 000000000..1867081aa --- /dev/null +++ b/web/documentserver-example/csharp/App_Themes/images/icon_pdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/documentserver-example/csharp/App_Themes/images/logo.png b/web/documentserver-example/csharp/App_Themes/images/logo.png deleted file mode 100644 index d433d89e2..000000000 Binary files a/web/documentserver-example/csharp/App_Themes/images/logo.png and /dev/null differ diff --git a/web/documentserver-example/csharp/App_Themes/images/mobile-logo.svg b/web/documentserver-example/csharp/App_Themes/images/mobile-logo.svg new file mode 100644 index 000000000..e8b8cb5d7 --- /dev/null +++ b/web/documentserver-example/csharp/App_Themes/images/mobile-logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/documentserver-example/csharp/App_Themes/images/mobile-menu.svg b/web/documentserver-example/csharp/App_Themes/images/mobile-menu.svg new file mode 100644 index 000000000..b40f11580 --- /dev/null +++ b/web/documentserver-example/csharp/App_Themes/images/mobile-menu.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/documentserver-example/csharp/App_Themes/images/plus.svg b/web/documentserver-example/csharp/App_Themes/images/plus.svg new file mode 100644 index 000000000..673a1f23d --- /dev/null +++ b/web/documentserver-example/csharp/App_Themes/images/plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/documentserver-example/csharp/App_Themes/media.css b/web/documentserver-example/csharp/App_Themes/media.css index 705085cc6..93baf3395 100644 --- a/web/documentserver-example/csharp/App_Themes/media.css +++ b/web/documentserver-example/csharp/App_Themes/media.css @@ -79,7 +79,8 @@ margin-left: 0; } - .tableRow { + .tableRow, + menu.links { width: 90%; } @@ -119,7 +120,8 @@ .contentCells-icon{ width: 5%; } - .tableRow { + .tableRow, + menu.links { width: 55%; } @@ -175,7 +177,8 @@ } @media (max-width: 715px) { - .tableRow { + .tableRow, + menu.links { width: 45%; } } @@ -263,7 +266,8 @@ padding-left: 0; } - .tableRow { + .tableRow, + menu.links { width: 75%; } @@ -293,7 +297,8 @@ width: 580px; } - .tableRow { + .tableRow, + menu.links { width: 95%; } @@ -407,7 +412,8 @@ padding: 16px 0 6px; } - .tableRow { + .tableRow, + menu.links { width: 40%; } @@ -502,6 +508,17 @@ justify-content: space-between; align-items: center; } + .buttonsMobile.indent { + margin-bottom: 0; + flex-wrap: nowrap; + } + .button.file-type:hover, + .button.file-type { + height: 28px; + width: 100px; + margin-bottom: 10px !important; + font-size: 9px; + } .button.gray{ margin: 0; } @@ -527,7 +544,8 @@ } @media (max-width: 510px) and (min-width: 470px) { - .tableRow { + .tableRow, + menu.links { width: 35%; } @@ -553,7 +571,8 @@ } @media (max-width: 470px) and (min-width: 420px) { - .tableRow { + .tableRow, + menu.links { width: 30%; } .tableRow td:first-child{ @@ -588,7 +607,8 @@ } @media (max-width: 420px) and (min-width: 320px) { - .tableRow { + .tableRow, + menu.links { width: 25%; } @@ -638,7 +658,8 @@ } } @media (max-width: 769px) and (min-width: 715px){ - .tableRow{ + .tableRow, + menu.links { width: 50%; } } @@ -687,3 +708,216 @@ max-width: none; } } + +@media (max-width: 592px) and (min-width: 320px) { + body.menu-open { + overflow: hidden; + } + + header { + min-width: auto; + height: fit-content; + } + + header a { + display: block; + } + + header img { + margin: 0; + } + + header, footer { + position: -webkit-sticky; /* Safari */ + position: sticky; + top: 0; + z-index: 100; + } + + .center { + width: 100%; + margin: 0; + } + + .left-panel { + background-color: rgba(186, 186, 186, 0.6); + display: none; + flex-direction: row; + align-items: start; + max-width: none; + width: 100%; + margin: 0; + position: fixed; + left: 0; + height: calc(100% - 124px); + z-index:99; + } + + .left-panel.active { + display: flex; + } + + .help-block { + height: 100%; + margin: 0; + background-color: #F5F5F5; + width: 248px; + padding-left: 16px; + padding-top: 33px; + padding-bottom: 33px; + padding-right: 40px; + box-sizing: border-box; + overflow-y: auto; + overflow-x: hidden; + } + + .table-main { + width: 100%; + } + + .mobile-close-btn { + display: block; + width: 48px; + height: 48px; + background-color: #E2E2E2; + border-radius: 2px; + border-color: #E2E2E2; + color: #808080; + cursor: pointer; + outline: inherit; + border: none; + } + + .main-panel { + width: 100%; + left: 0; + padding: 28px 16px; + } + + #portal-info { + width: 100%; + max-width: fit-content; + } + + menu.links { + width: 100%; + margin-top: 0; + padding: 0; + } + + span.portal-name { + font-size: 16px; + } + + span.portal-descr:first-child { + font-size: 13px; + } + + span.portal-descr { + font-size: 12px; + } + + .user-descr { + width: 100%; + max-width: none; + min-width: auto; + border-bottom: 1px solid #E5E5E5; + padding: 12px 0; + margin: 0; + cursor: pointer; + } + + .user-descr ul { + display: none; + } + + .user-descr ul.active { + display: block; + } + + .user-descr b { + font-size: 13px; + display: flex; + align-items: center; + column-gap: 8px; + margin: 0; + } + + .user-descr b::before { + content: url("images/plus.svg"); + display: inline-block; + width: 24px; + height: 24px; + } + + .storedHeader { + width: 100%; + } + + .storedHeaderClearAll { + padding-right: 0; + } + + .scroll-table-body { + top: 36px; + } + + .scroll-table-body tr:first-child { + padding-top: 0; + } + + .tableRow { + border-bottom: 1px solid #e5e5e5; + padding: 16px 0; + width: 100%; + } + + .tableRow td:first-child { + width: 100%; + } + + .stored-edit span { + font-size: 14px; + } + + .header-list { + font-size: 16px; + } + + .firstContentCellViewers { + border-bottom: none !important; + } + + .firstContentCellViewers ~ td { + border-bottom: none !important; + } + + .downloadContentCellShift:after { + display: none; + } + + .main-nav { + display: none; + } + + .responsive-nav { + height: 44px; + display: flex; + flex-direction: row; + margin: 0; + align-items: center; + column-gap: 16px; + padding: 10px 16px; + width: 100%; + box-sizing: border-box; + list-style: none; + } + + .main { + height: calc(100% - 124px); + } + + .user-block-table { + height: auto; + } +} diff --git a/web/documentserver-example/csharp/App_Themes/stylesheet.css b/web/documentserver-example/csharp/App_Themes/stylesheet.css index 69c00e00b..71f7e3751 100644 --- a/web/documentserver-example/csharp/App_Themes/stylesheet.css +++ b/web/documentserver-example/csharp/App_Themes/stylesheet.css @@ -65,6 +65,14 @@ header img { margin: 10px 0 22px 32px; } +.responsive-nav { + display: none; +} + +.mobile-close-btn { + display: none; +} + .center { position: relative; margin: 0 auto 0; @@ -164,7 +172,7 @@ label .checkbox { } .try-editor.form { - background-image: url("images/file_docxf.svg"); + background-image: url("images/file_pdf.svg"); } .side-option { @@ -230,6 +238,33 @@ label .checkbox { color: #FF6F3D; } +.button.file-type { + font-size: 11px; + color: #FFFFFF; + padding: 8px 8px; + margin-right: 10px; +} + +.button.file-type.disable { + cursor: default; +} + +.button.file-type.pale { + opacity: 30%; +} + +.button.file-type.document { + background: #446995; +} + +.button.file-type.spreadsheet { + background: #40865C; +} + +.button.file-type.presentation { + background: #AA5252; +} + .upload-panel { float: left; padding: 24px 0; @@ -271,6 +306,43 @@ label .checkbox { border-bottom: 1px solid #D0D5DA; } +.links { + display: flex; + padding: 0; + column-gap: 30px; + align-items: center; + list-style: none; + border-bottom: 1px solid #E2E2E2; + margin: 0; + margin-bottom: 24px; +} + +.links li { + padding: 4px; + border-bottom: 2px solid transparent; + margin-bottom: -1px; +} + +.links li.active { + border-bottom: 2px solid #FF6F3D; +} + +.links li.active a { + color: #FF6F3D; +} + +.links li.active a img { + filter: invert(55%) sepia(67%) saturate(2727%) hue-rotate(335deg) brightness(104%) contrast(101%); +} + +.links a { + display: inline-block; + padding: 2px 0; + line-height: 20px; + font-size: 13px; + text-decoration: none; +} + #mainProgress { color: #333333; display: none; @@ -468,6 +540,11 @@ footer a:hover { background-image: url("images/icon_pptx.svg"); } +.stored-edit.pdf, +.uploadFileName.pdf { + background-image: url("images/icon_pdf.svg"); +} + .stored-edit span { font-size: 12px; line-height: 12px; @@ -596,6 +673,29 @@ footer a:hover { width: 4%; } +.storedHeader { + display: flex; + width: 100%; + justify-content: space-between; + align-items: center; +} + +.storedHeaderClearAll { + padding-right: 52px; +} + +.clear-all { + display: inline-block; + width: 100px; + padding: 2px; + outline: 1px solid #E5E5E5; + text-align: center; + cursor:pointer; + text-transform: uppercase; + background-color: #F5F5F5; + color: #666666; +} + .select-user { color: #444444; font-family: Open Sans; @@ -745,6 +845,16 @@ html { margin-left: 25px; } +.buttonsMobile.indent{ + padding-left: 35px; + margin-top: 10px; + margin-bottom: 10px; +} + +.invisible { + display: none; +} + .tooltip { background: #FFFFFF; border-radius: 5px; diff --git a/web/documentserver-example/csharp/Default.aspx b/web/documentserver-example/csharp/Default.aspx index 4bb5b0dfa..bffd08d9b 100644 --- a/web/documentserver-example/csharp/Default.aspx +++ b/web/documentserver-example/csharp/Default.aspx @@ -45,11 +45,23 @@
      -
      @@ -71,7 +83,7 @@ Presentation
    • - PDF form + PDF form
    • +
      <% var storedFiles = GetStoredFiles(); %>
      + + + <% if (bool.Parse(WebConfigurationManager.AppSettings["enable-forgotten"])) { %> +
    • + Forgotten files +
    • + <% } %> +
      "> ONLYOFFICE Document Editors – Welcome! @@ -138,7 +165,7 @@ You can open the same document using different users in different Web browser sessions, so you can check out multi-user editing functions. <% foreach (User user in Users.getAllUsers()) { %> -
      +
      <%= user.name.IsEmpty() ? "Anonymous" : user.name %>
        <% foreach (string description in user.descriptions) @@ -153,7 +180,14 @@ if (storedFiles.Any()) { %>
        - Your documents +
        +
        + Your documents +
        +
        +
        Clear all
        +
        +
        @@ -195,11 +229,13 @@ Open in editor for mobile devices - + <% if (docType != "pdf") { %> + + <% } %> <% if (docType == "word") { %>
        - " target="_blank"> - Open in editor for comment - - + " target="_blank"> + Open in editor for comment + + " target="_blank"> @@ -297,6 +333,15 @@
        After these steps are completed, you can work with your document.
        1. Loading the file. The loading speed depends on file size and additional elements it contains. +
        2. Conversion. The file is converted to OOXML so that you can edit it. @@ -364,12 +409,8 @@ + - diff --git a/web/documentserver-example/csharp/Default.aspx.cs b/web/documentserver-example/csharp/Default.aspx.cs index da98636f9..dfa6a4c55 100644 --- a/web/documentserver-example/csharp/Default.aspx.cs +++ b/web/documentserver-example/csharp/Default.aspx.cs @@ -250,6 +250,7 @@ public static string DocumentType(string fileName) { var ext = Path.GetExtension(fileName).ToLower(); + if (FormatManager.PdfExtensions().Contains(ext)) return "pdf"; // pdf for pdf extensions if (FormatManager.DocumentExtensions().Contains(ext)) return "word"; // word for text document extensions if (FormatManager.SpreadsheetExtensions().Contains(ext)) return "cell"; // cell for spreadsheet extensions if (FormatManager.PresentationExtensions().Contains(ext)) return "slide"; // slide for presentation extensions @@ -437,7 +438,14 @@ public static string DoConvert(HttpContext context) var lang = context.Request.Cookies.GetOrDefault("ulang", null); var extension = (Path.GetExtension(_fileName).ToLower() ?? "").Trim('.'); - var internalExtension = "ooxml"; + string conversionExtension = "ooxml"; // set the default conversion extension as ooxml + object fileExt; + + // change the conversion extension if it was provided in the request body + if (body.TryGetValue("fileExt", out fileExt) && !String.IsNullOrEmpty(fileExt.ToString())) + { + conversionExtension = fileExt.ToString(); + } // check if the file with such an extension can be converted if (ConvertExts.Contains("." + extension)) @@ -454,7 +462,7 @@ public static string DoConvert(HttpContext context) // get the url and file type of the converted file Dictionary newFileData; - var result = ServiceConverter.GetConvertedData(fileUrl.ToString() , extension, internalExtension, key, true, out newFileData, filePass, lang); + var result = ServiceConverter.GetConvertedData(fileUrl.ToString() , extension, conversionExtension, key, true, out newFileData, filePass, lang); if (result != 100) { return "{ \"step\" : \"" + result + "\", \"filename\" : \"" + _fileName + "\"}"; diff --git a/web/documentserver-example/csharp/DocEditor.aspx b/web/documentserver-example/csharp/DocEditor.aspx index 12759feb9..fac16c6c3 100644 --- a/web/documentserver-example/csharp/DocEditor.aspx +++ b/web/documentserver-example/csharp/DocEditor.aspx @@ -126,7 +126,7 @@ // the meta information of the document is changed via the meta command var onMetaChange = function (event) { - if (event.data.favorite) { + if (event.data.favorite !== undefined) { var favorite = !!event.data.favorite; var title = document.title.replace(/^\☆/g, ""); document.title = (favorite ? "☆" : "") + title; @@ -233,6 +233,41 @@ } }; + var onRequestUsers = function (event) { + if (event && event.data){ + var c = event.data.c; + } + switch (c) { + case "info": + users = []; + var allUsers = <%= UsersInfo %>; + for (var i = 0; i < event.data.id.length; i++) { + for (var j = 0; j < allUsers.length; j++) { + if (allUsers[j].id == event.data.id[i]) { + users.push(allUsers[j]); + break; + } + } + } + break; + case "protect": + var users = <%= UsersForProtect %>; + break; + default: + users = <%= UsersForMentions %>; + } + docEditor.setUsers({ + "c": c, + "users": users, + }); + }; + + var onRequestSendNotify = function (event) { + event.data.actionLink = replaceActionLink(location.href, JSON.stringify(event.data.actionLink)); + var data = JSON.stringify(event.data); + innerAlert("onRequestSendNotify: " + data); + }; + config = <%= DocConfig %>; config.width = "100%"; @@ -275,65 +310,37 @@ docEditor.setHistoryData(JSON.parse(xhr.responseText)); // send the link to the document for viewing the version history } }; - config.events['onRequestHistoryClose'] = function () { // the user is trying to go back to the document from viewing the document version history - document.location.reload(); - }; - config.events['onRequestRestore'] = function (event) { - var fileName = "<%= FileName %>"; - var version = event.data.version; - var data = { - fileName: fileName, - version: version + if (config.editorConfig.user.id !== "uid-3") { + config.events['onRequestHistoryClose'] = function () { // the user is trying to go back to the document from viewing the document version history + document.location.reload(); }; - - let xhr = new XMLHttpRequest(); - xhr.open("POST", "webeditor.ashx?type=restore&directUrl=" + !!config.document.directUrl); - xhr.setRequestHeader('Content-Type', 'application/json'); - xhr.send(JSON.stringify(data)); - xhr.onload = function () { - docEditor.refreshHistory(JSON.parse(xhr.responseText)); - } - }; + config.events['onRequestRestore'] = function (event) { + var fileName = "<%= FileName %>"; + var version = event.data.version; + var data = { + fileName: fileName, + version: version + }; + + let xhr = new XMLHttpRequest(); + xhr.open("POST", "webeditor.ashx?type=restore&directUrl=" + !!config.document.directUrl); + xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.send(JSON.stringify(data)); + xhr.onload = function () { + docEditor.refreshHistory(JSON.parse(xhr.responseText)); + } + }; + } // add mentions for not anonymous users <% if (!string.IsNullOrEmpty(UsersForMentions)) { %> - config.events['onRequestUsers'] = function (event) { - if (event && event.data){ - var c = event.data.c; - } - switch (c) { - case "info": - users = []; - var allUsers = <%= UsersInfo %>; - for (var i = 0; i < event.data.id.length; i++) { - for (var j = 0; j < allUsers.length; j++) { - if (allUsers[j].id == event.data.id[i]) { - users.push(allUsers[j]); - break; - } - } - } - break; - case "protect": - var users = <%= UsersForProtect %>; - break; - default: - users = <%= UsersForMentions %>; - } - docEditor.setUsers({ - "c": c, - "users": users, - }); - }; + config.events['onRequestUsers'] = onRequestUsers; <% } %> + config.events['onRequestSaveAs'] = onRequestSaveAs; // the user is mentioned in a comment - config.events['onRequestSendNotify'] = function (event) { - event.data.actionLink = replaceActionLink(location.href, JSON.stringify(event.data.actionLink)); - var data = JSON.stringify(event.data); - innerAlert("onRequestSendNotify: " + data); - }; + config.events['onRequestSendNotify'] = onRequestSendNotify; // prevent file renaming for anonymous users config.events['onRequestRename'] = onRequestRename; config.events['onRequestReferenceData'] = onRequestReferenceData; @@ -342,17 +349,7 @@ config.events['onRequestOpen'] = onRequestOpen; } - if (config.editorConfig.createUrl) { - config.events.onRequestSaveAs = onRequestSaveAs; - }; - var сonnectEditor = function () { - if ((config.document.fileType === "docxf" || config.document.fileType === "oform") - && DocsAPI.DocEditor.version().split(".")[0] < 7) { - innerAlert("Please update ONLYOFFICE Docs to version 7.0 to work on fillable forms online."); - return; - } - docEditor = new DocsAPI.DocEditor("iframeEditor", config); }; diff --git a/web/documentserver-example/csharp/DocEditor.aspx.cs b/web/documentserver-example/csharp/DocEditor.aspx.cs index 260246d66..cd878f9e7 100755 --- a/web/documentserver-example/csharp/DocEditor.aspx.cs +++ b/web/documentserver-example/csharp/DocEditor.aspx.cs @@ -135,14 +135,14 @@ protected void Page_Load(object sender, EventArgs e) { // create demo document of a specified file type Try(type, Request["sample"], Request); - Response.Redirect("doceditor.aspx?fileID=" + HttpUtility.UrlEncode(FileName)); + Response.Redirect("doceditor.aspx?editorsMode=edit&fileID=" + HttpUtility.UrlEncode(FileName)); } // get file extension var ext = Path.GetExtension(FileName).ToLower(); - + var canFill = _Default.FillFormsExts.Contains(ext); // get editor mode or set the default one (edit) - var editorsMode = Request.GetOrDefault("editorsMode", "edit"); + var editorsMode = Request.GetOrDefault("editorsMode", canFill ? "fillForms" : "edit"); var canEdit = _Default.EditedExts.Contains(ext); // check if this file can be edited var editorsType = Request.GetOrDefault("editorsType", "desktop"); @@ -150,11 +150,11 @@ protected void Page_Load(object sender, EventArgs e) var id = Request.Cookies.GetOrDefault("uid", null); var user = Users.getUser(id); // get the user - if ((!canEdit && editorsMode.Equals("edit") || editorsMode.Equals("fillForms")) && _Default.FillFormsExts.Contains(ext)) { + if ((!canEdit && editorsMode.Equals("edit") || editorsMode.Equals("fillForms")) && canFill) { editorsMode = "fillForms"; canEdit = true; } - var submitForm = editorsMode.Equals("fillForms") && id.Equals("uid-1"); // check if the Submit form button is displayed or hidden + var submitForm = (editorsMode.Equals("fillForms") || editorsMode.Equals("embedded")) && user.id.Equals("uid-1"); // check if the Submit form button is displayed or hidden var mode = canEdit && editorsMode != "view" ? "edit" : "view"; // get the editor opening mode (edit or view) var jss = new JavaScriptSerializer(); @@ -282,10 +282,12 @@ protected void Page_Load(object sender, EventArgs e) { "forcesave", false }, // adds the request for the forced file saving to the callback handler { "submitForm", submitForm }, // if the Submit form button is displayed or not { - "goback", new Dictionary // settings for the Open file location menu button and upper right corner button + "goback", user.goback != null ? new Dictionary // settings for the Open file location menu button and upper right corner button { - { "url", _Default.GetServerUrl(false) + "default.aspx" } // the absolute URL to the website address which will be opened when clicking the Open file location menu button - } + { "url", _Default.GetServerUrl(false) + "default.aspx" }, // the absolute URL to the website address which will be opened when clicking the Open file location menu button + { "text", user.goback.text }, + { "blank", user.goback.blank } + } : new Dictionary{} } } } @@ -338,17 +340,17 @@ private Dictionary GetLogoConfig() var InsertImageUrl = new UriBuilder(_Default.GetServerUrl(true)); InsertImageUrl.Path = HttpRuntime.AppDomainAppVirtualPath + (HttpRuntime.AppDomainAppVirtualPath.EndsWith("/") ? "" : "/") - + "App_Themes\\images\\logo.png"; + + "App_Themes\\images\\logo.svg"; var DirectImageUrl = new UriBuilder(_Default.GetServerUrl(false)); DirectImageUrl.Path = HttpRuntime.AppDomainAppVirtualPath + (HttpRuntime.AppDomainAppVirtualPath.EndsWith("/") ? "" : "/") - + "App_Themes\\images\\logo.png"; + + "App_Themes\\images\\logo.svg"; // create a logo config Dictionary logoConfig = new Dictionary { - { "fileType", "png"}, + { "fileType", "svg"}, { "url", InsertImageUrl.ToString()} }; @@ -487,8 +489,8 @@ private static void Try(string type, string sample, HttpRequest request) case "slide": ext = ".pptx"; // .pptx for slide document type break; - case "docxf": - ext = ".docxf"; + case "pdf": + ext = ".pdf"; break; default: return; diff --git a/web/documentserver-example/csharp/DocumentConverter.cs b/web/documentserver-example/csharp/DocumentConverter.cs index a59c2495d..580f77afb 100644 --- a/web/documentserver-example/csharp/DocumentConverter.cs +++ b/web/documentserver-example/csharp/DocumentConverter.cs @@ -231,6 +231,10 @@ private static void ProcessResponseError(int errorCode) switch (errorCode) { + case -9: + // public const int c_nErrorConversionOutputFormatError = -9; + errorMessage = String.Format(errorMessageTemplate, "Error conversion output format"); + break; case -8: // public const int c_nErrorFileVKey = -8; errorMessage = String.Format(errorMessageTemplate, "Error document VKey"); diff --git a/web/documentserver-example/csharp/Forgotten.aspx b/web/documentserver-example/csharp/Forgotten.aspx new file mode 100755 index 000000000..cbf6f3eb4 --- /dev/null +++ b/web/documentserver-example/csharp/Forgotten.aspx @@ -0,0 +1,147 @@ +<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Forgotten.aspx.cs" Inherits="OnlineEditorsExample.Forgotten" Title="ONLYOFFICE" %> + +<%@ Import Namespace="System.IO" %> +<%@ Import Namespace="OnlineEditorsExample" %> + + + + + + + + /> + ONLYOFFICE + + + + + + + + + + + + + + +
        + + +
      • + + ONLYOFFICE + +
      • +
      • + + ONLYOFFICE + +
      • +
        +
        +
        + + + + + + + +
        +
        + + +
      • + Forgotten files +
      • +
        +
        +
        +
        + Forgotten files +
        +
        + + + + + + + +
        FilenameAction
        +
        + + + <% foreach (var file in GetForgottenFiles()) { %> + "> + + + + <% } %> + +
        + " href="<%= file["url"] %>" target="_blank"> + <%= file["key"] %> + + + "> + Download + "> + Delete +
        +
        +
        +
        +
        +
        + + + + + + + + diff --git a/web/documentserver-example/csharp/Forgotten.aspx.cs b/web/documentserver-example/csharp/Forgotten.aspx.cs new file mode 100755 index 000000000..96b9a77b1 --- /dev/null +++ b/web/documentserver-example/csharp/Forgotten.aspx.cs @@ -0,0 +1,97 @@ +/** + * + * (c) Copyright Ascensio System SIA 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Web.Configuration; +using System.Web.UI; + +namespace OnlineEditorsExample +{ + public partial class Forgotten : Page + { + + //get server version + public static string GetVersion() + { + return WebConfigurationManager.AppSettings["version"]; + } + + private static bool? _ismono; + + public static bool IsMono + { + get { return _ismono.HasValue ? _ismono.Value : (_ismono = (bool?)(Type.GetType("Mono.Runtime") != null)).Value; } + } + + // get the document type + public static string DocumentType(string fileName) + { + var ext = Path.GetExtension(fileName).ToLower(); + + if (FormatManager.PdfExtensions().Contains(ext)) return "pdf"; // pdf for pdf extensions + if (FormatManager.DocumentExtensions().Contains(ext)) return "word"; // word for text document extensions + if (FormatManager.SpreadsheetExtensions().Contains(ext)) return "cell"; // cell for spreadsheet extensions + if (FormatManager.PresentationExtensions().Contains(ext)) return "slide"; // slide for presentation extensions + + return "word"; // the default document type is word + } + + protected void Page_Load(object sender, EventArgs e) + { + if (!bool.Parse(WebConfigurationManager.AppSettings["enable-forgotten"])) + { + Response.Clear(); + Response.StatusCode = 403; + Response.End(); + } + } + + // fetch forgotten files from the document server + public static List> GetForgottenFiles() + { + var files = new List>(); + + try + { + var response = TrackManager.commandRequest("getForgottenList", null); + ArrayList keys = (ArrayList) response["keys"]; + + // fetch all the forgotten files from the document server + foreach (string key in keys) + { + var file = new Dictionary(); + var fileResult = TrackManager.commandRequest("getForgotten", key); + file.Add("key", fileResult["key"].ToString()); + file.Add("url", fileResult["url"].ToString()); + file.Add("type", DocumentType(fileResult["url"].ToString())); + + files.Add(file); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(ex.Message); + } + + return files; + } + } +} \ No newline at end of file diff --git a/web/documentserver-example/csharp/Forgotten.aspx.designer.cs b/web/documentserver-example/csharp/Forgotten.aspx.designer.cs new file mode 100755 index 000000000..5e04674f7 --- /dev/null +++ b/web/documentserver-example/csharp/Forgotten.aspx.designer.cs @@ -0,0 +1,24 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace OnlineEditorsExample { + + + public partial class Forgotten { + + /// + /// form1 control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.HtmlControls.HtmlForm form1; + } +} diff --git a/web/documentserver-example/csharp/FormatManager.cs b/web/documentserver-example/csharp/FormatManager.cs index a845a20fa..5c70d3b24 100644 --- a/web/documentserver-example/csharp/FormatManager.cs +++ b/web/documentserver-example/csharp/FormatManager.cs @@ -150,6 +150,20 @@ public static List Documents() .ToList(); } + public static List PdfExtensions() + { + return Pdfs() + .Select(format => format.Extension()) + .ToList(); + } + + public static List Pdfs() + { + return All() + .Where(format => format.Type == "pdf") + .ToList(); + } + public static List AllExtensions() { return All() diff --git a/web/documentserver-example/csharp/OnlineEditorsExample.csproj b/web/documentserver-example/csharp/OnlineEditorsExample.csproj index 2206a4696..571806a20 100644 --- a/web/documentserver-example/csharp/OnlineEditorsExample.csproj +++ b/web/documentserver-example/csharp/OnlineEditorsExample.csproj @@ -77,6 +77,7 @@ + @@ -94,6 +95,7 @@ + @@ -104,10 +106,16 @@ + + + - + + + + @@ -119,6 +127,12 @@ DocEditor.aspx + + Forgotten.aspx + + + Forgotten.aspx + @@ -145,7 +159,6 @@ - diff --git a/web/documentserver-example/csharp/TrackManager.cs b/web/documentserver-example/csharp/TrackManager.cs index 5ac89a80f..c4d185261 100644 --- a/web/documentserver-example/csharp/TrackManager.cs +++ b/web/documentserver-example/csharp/TrackManager.cs @@ -285,7 +285,7 @@ public static int processForceSave(Dictionary fileData, string f } // create a command request - public static void commandRequest(string method, string key, object meta = null) + public static Dictionary commandRequest(string method, string key, object meta = null) { _Default.VerifySSL(); @@ -348,6 +348,7 @@ public static void commandRequest(string method, string key, object meta = null) { throw new Exception(dataResponse); } + return responseObj; } private static void SaveFile(byte[] data, string path) diff --git a/web/documentserver-example/csharp/Users.cs b/web/documentserver-example/csharp/Users.cs index e3ffe3662..c9ccebffb 100644 --- a/web/documentserver-example/csharp/Users.cs +++ b/web/documentserver-example/csharp/Users.cs @@ -58,7 +58,9 @@ public class Users "Can’t print the file", "Can create new files from the editor", "Can see the information about Group2 users", - "Can’t submit forms" + "Can’t submit forms", + "Can't close history", + "Can't restore the file version" }; static List descr_user_0 = new List() @@ -91,7 +93,8 @@ public class Users new List(), descr_user_1, true, - true + true, + new Goback(null, false) ), new User( "uid-2", @@ -110,7 +113,8 @@ public class Users new List(), descr_user_2, false, - true + true, + new Goback("Go to Documents",null) ), new User( "uid-3", @@ -129,7 +133,8 @@ public class Users new List() { "copy", "download", "print" }, descr_user_3, false, - false + false, + null ), new User( "uid-0", @@ -143,7 +148,8 @@ public class Users new List() { "protect" }, descr_user_0, false, - false + false, + null ) }; @@ -235,8 +241,9 @@ public class User public bool templates; public List userInfoGroups; public bool avatar; + public Goback goback; - public User(string id, string name, string email, string group, List reviewGroups, Dictionary commentGroups, List userInfoGroups, bool? favorite, List deniedPermissions, List descriptions, bool templates, bool avatar) + public User(string id, string name, string email, string group, List reviewGroups, Dictionary commentGroups, List userInfoGroups, bool? favorite, List deniedPermissions, List descriptions, bool templates, bool avatar, Goback goback) { this.id = id; this.name = name; @@ -250,6 +257,21 @@ public User(string id, string name, string email, string group, List rev this.templates = templates; this.userInfoGroups = userInfoGroups; this.avatar = avatar; + this.goback = goback; + } + } + + public class Goback + { + public string text; + public bool? blank; + + public Goback(){} + + public Goback(string text, bool? blank) + { + this.text = text; + this.blank = blank; } } } diff --git a/web/documentserver-example/csharp/WebEditor.ashx.cs b/web/documentserver-example/csharp/WebEditor.ashx.cs index b1d374453..c208a9d13 100644 --- a/web/documentserver-example/csharp/WebEditor.ashx.cs +++ b/web/documentserver-example/csharp/WebEditor.ashx.cs @@ -69,6 +69,9 @@ public void ProcessRequest(HttpContext context) case "remove": Remove(context); break; + case "removeforgotten": + RemoveForgotten(context); + break; case "assets": Assets(context); break; @@ -87,6 +90,9 @@ public void ProcessRequest(HttpContext context) case "reference": Reference(context); break; + case "formats": + Formats(context); + break; } } @@ -222,12 +228,22 @@ private static void Remove(HttpContext context) context.Response.ContentType = "text/plain"; try { - var fileName = Path.GetFileName(context.Request["fileName"]); - var path = _Default.StoragePath(fileName, HttpUtility.UrlEncode(_Default.CurUserHostAddress(HttpContext.Current.Request.UserHostAddress))); - var histDir = _Default.HistoryDir(path); + string fileName = context.Request["fileName"]; + string userAddress = HttpUtility.UrlEncode(_Default.CurUserHostAddress(HttpContext.Current.Request.UserHostAddress)); + + if (!String.IsNullOrEmpty(fileName)) + { + fileName = Path.GetFileName(fileName); + var path = _Default.StoragePath(fileName, userAddress); + var histDir = _Default.HistoryDir(path); - if (File.Exists(path)) File.Delete(path); // delete file - if (Directory.Exists(histDir)) Directory.Delete(histDir, true); // delete file history + if (File.Exists(path)) File.Delete(path); // delete file + if (Directory.Exists(histDir)) Directory.Delete(histDir, true); // delete file history + } else + { + string userDir = _Default.StoragePath("", userAddress); + if (Directory.Exists(userDir)) Directory.Delete(userDir, true); // delete the user's directory + } context.Response.Write("{ \"success\": true }"); } @@ -778,5 +794,50 @@ private static string MakePublicHistoryUrl(string filename, string version, stri + userAddress; return fileUrl.ToString(); } + + // return all the supported formats + private static void Formats(HttpContext context) + { + try + { + Dictionary data = new Dictionary + { + { "formats", FormatManager.All() } + }; + context.Response.ContentType = "application/json"; + var jss = new JavaScriptSerializer(); + + context.Response.Write(jss.Serialize(data)); + } + catch (Exception e) + { + context.Response.Write("{ \"error\": \"" + e.Message + "\"}"); + } + } + + // delete a forgotten file from the document server + private static void RemoveForgotten(HttpContext context) + { + try + { + if (!bool.Parse(WebConfigurationManager.AppSettings["enable-forgotten"])) + { + throw new HttpException(403, "The forgotten page is disabled"); + } + + string filename = context.Request["filename"]; + + if (!String.IsNullOrEmpty(filename)) + { + TrackManager.commandRequest("deleteForgotten", filename); + } + + context.Response.StatusCode = 204; + } + catch (Exception e) + { + context.Response.Write("{ \"error\": \"" + e.Message + "\"}"); + } + } } } \ No newline at end of file diff --git a/web/documentserver-example/csharp/assets/document-formats b/web/documentserver-example/csharp/assets/document-formats index 2c3e099f1..dbd7ce514 160000 --- a/web/documentserver-example/csharp/assets/document-formats +++ b/web/documentserver-example/csharp/assets/document-formats @@ -1 +1 @@ -Subproject commit 2c3e099f1e8eaaf7070cdb81643de9586a2235e2 +Subproject commit dbd7ce514589be824ba9c3a4793c00937a35947e diff --git a/web/documentserver-example/csharp/assets/document-templates b/web/documentserver-example/csharp/assets/document-templates index 1fc823afa..c9fc1ee6b 160000 --- a/web/documentserver-example/csharp/assets/document-templates +++ b/web/documentserver-example/csharp/assets/document-templates @@ -1 +1 @@ -Subproject commit 1fc823afa909e4c49551e4e5b945189a21ff1999 +Subproject commit c9fc1ee6beac118a7d9472f971a61cd2eb285293 diff --git a/web/documentserver-example/csharp/script/forgotten.js b/web/documentserver-example/csharp/script/forgotten.js new file mode 100755 index 000000000..e5a9af2b3 --- /dev/null +++ b/web/documentserver-example/csharp/script/forgotten.js @@ -0,0 +1,19 @@ +function deleteFile(event) { + let filename = event.currentTarget.getAttribute("data"); + filename = encodeURIComponent(filename); + let url = `webeditor.ashx?type=removeforgotten&filename=${filename}`; + + fetch(url, { + headers: { + "Content-Type": "application/json", + } + }).then(result => { + if (result.status == 204) { + document.location.reload(true); + } + }); +} + +document.querySelectorAll('.delete-file').forEach(el => { + el.addEventListener('click', deleteFile); +}); \ No newline at end of file diff --git a/web/documentserver-example/csharp/script/formats.js b/web/documentserver-example/csharp/script/formats.js new file mode 100644 index 000000000..e423eeb20 --- /dev/null +++ b/web/documentserver-example/csharp/script/formats.js @@ -0,0 +1,66 @@ +/** + * + * (c) Copyright Ascensio System SIA 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +class Format { + constructor(name, type, actions, convert, mime) { + this.name = name; + this.type = type; + this.actions = actions; + this.convert = convert; + this.mime = mime; + } + + isAutoConvertible() { + return this.actions.includes('auto-convert'); + } + + isEditable() { + return this.actions.includes('edit') || this.actions.includes('lossy-edit'); + } + + isFillable() { + return this.actions.includes('fill'); + } +} + +class FormatManager { + formats = []; + + constructor(formats) { + if(Array.isArray(formats)) this.formats = formats; + } + + findByExtension(extension) { + return this.formats.find(format => format.name == extension); + } + + isAutoConvertible(extension) { + let format = this.findByExtension(extension); + return format !== undefined && format.isAutoConvertible(); + } + + isEditable(extension) { + let format = this.findByExtension(extension); + return format !== undefined && format.isEditable(); + } + + isFillable(extension) { + let format = this.findByExtension(extension); + return format !== undefined && format.isFillable(); + } +} \ No newline at end of file diff --git a/web/documentserver-example/csharp/script/jscript.js b/web/documentserver-example/csharp/script/jscript.js index 2491c4002..4f8324837 100644 --- a/web/documentserver-example/csharp/script/jscript.js +++ b/web/documentserver-example/csharp/script/jscript.js @@ -17,6 +17,27 @@ */ var directUrl; +var formatManager; + +window.onload = function () { + fetch("webeditor.ashx?type=formats") + .then((response) => response.json()) + .then((data) => { + if (data.formats) { + let formats = []; + data.formats.forEach(format => { + formats.push(new Format( + format.Name, + format.Type, + format.Actions, + format.Convert, + format.Mime + )); + }); + formatManager = new FormatManager(formats); + } + }) +} if (typeof jQuery != "undefined") { jq = jQuery.noConflict(); @@ -87,8 +108,8 @@ if (typeof jQuery != "undefined") { }); var timer = null; - var checkConvert = function (filePass) { - filePass = filePass ? filePass : null; + var checkConvert = function (filePass, fileType) { + filePass = filePass ? filePass : null; if (timer != null) { clearTimeout(timer); } @@ -100,10 +121,10 @@ if (typeof jQuery != "undefined") { jq("#filePass").val(""); var fileName = jq("#hiddenFileName").val(); - var posExt = fileName.lastIndexOf('.'); + var posExt = fileName.lastIndexOf('.') + 1; posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : ''; - if (ConverExtList.indexOf(posExt) == -1) { + if (!formatManager.isAutoConvertible(posExt)) { jq("#step2").addClass("done").removeClass("current"); loadScripts(); return; @@ -116,7 +137,7 @@ if (typeof jQuery != "undefined") { contentType: "text/xml", type: "post", dataType: "json", - data: JSON.stringify({ filename: fileName, filePass: filePass }), + data: JSON.stringify({ filename: fileName, filePass: filePass, fileExt: fileType }), url: requestAddress, complete: function (data) { var responseText = data.responseText; @@ -132,6 +153,12 @@ if (typeof jQuery != "undefined") { } return; } else { + if (response.error.includes("Error conversion output format")) { + jq("#select-file-type").removeClass("invisible"); + jq("#step2").removeClass("current"); + jq("#hiddenFileName").attr("placeholder", filePass); + return; + } jq(".current").removeClass("current"); jq(".step:not(.done)").addClass("error"); jq("#mainProgress .error-message").show().find("span").text(response.error); @@ -143,7 +170,7 @@ if (typeof jQuery != "undefined") { jq("#hiddenFileName").val(response.filename); if (response.step && response.step < 100) { - checkConvert(filePass); + checkConvert(filePass, fileType); } else { jq("#step2").addClass("done").removeClass("current"); loadScripts(); @@ -178,10 +205,10 @@ if (typeof jQuery != "undefined") { jq("#beginView, #beginEmbedded").removeClass("disable"); var fileName = jq("#hiddenFileName").val(); - var posExt = fileName.lastIndexOf('.'); + var posExt = fileName.lastIndexOf('.') + 1; posExt = 0 <= posExt ? fileName.substring(posExt).trim().toLowerCase() : ''; - if (EditedExtList.indexOf(posExt) != -1 || FillFormExtList.indexOf(posExt) != -1) { + if (formatManager.isEditable(posExt) || formatManager.isFillable(posExt)) { jq("#beginEdit").removeClass("disable"); } }; @@ -213,6 +240,15 @@ if (typeof jQuery != "undefined") { }); }; + jq(document).on("click", ".file-type:not(.disable)", function () { + const currentElement = jq(this); + var fileType = currentElement.attr("data"); + var filePass = jq("#hiddenFileName").attr("placeholder"); + jq('.file-type').addClass(["disable", "pale"]); + currentElement.removeClass("pale"); + checkConvert(filePass, fileType); + }); + jq(document).on("click", "#enterPass", function () { var filePass = jq("#filePass").val(); if (filePass) { @@ -293,6 +329,24 @@ if (typeof jQuery != "undefined") { }); }); + jq(document).on("click", ".clear-all", function () { + if (confirm("Delete all the files?")) { + var requestAddress = "webeditor.ashx" + + "?type=remove"; + + jq.ajax({ + async: true, + contentType: "text/xml", + url: requestAddress, + complete: function (data) { + if (JSON.parse(data.responseText).success) { + window.location.reload(true); + } + } + }); + } + }); + function showUserTooltip (isMobile) { if ( jq("div#portal-info").is(":hidden") ) { jq("div#portal-info").show(); @@ -367,4 +421,27 @@ if (typeof jQuery != "undefined") { }).mouseout(function () { jq("div.tooltip").remove(); }); +} + +function toggleSidePanel(event) { + event.preventDefault(); + let sidePanel = document.querySelector(".left-panel"); + let body = document.querySelector("body"); + if (sidePanel.classList.contains("active")) { + sidePanel.classList.remove("active"); + body.classList.remove("menu-open"); + } else { + sidePanel.classList.add("active") + body.classList.add("menu-open"); + } +} + +function toggleUserDescr(event) { + let list = event.currentTarget.querySelector("ul"); + let cursor = window.getComputedStyle(event.currentTarget).getPropertyValue("cursor"); + + if (cursor === "pointer") { + if (list.classList.contains("active")) list.classList.remove("active"); + else list.classList.add("active"); + } } \ No newline at end of file diff --git a/web/documentserver-example/csharp/settings.config b/web/documentserver-example/csharp/settings.config index f788c9ea0..ac30df391 100644 --- a/web/documentserver-example/csharp/settings.config +++ b/web/documentserver-example/csharp/settings.config @@ -1,11 +1,12 @@ - + + diff --git a/web/documentserver-example/java-spring/3rd-Party.license b/web/documentserver-example/java-spring/3rd-Party.license index bd056534b..ccb8e57ec 100755 --- a/web/documentserver-example/java-spring/3rd-Party.license +++ b/web/documentserver-example/java-spring/3rd-Party.license @@ -1,6 +1,5 @@ ONLYOFFICE Applications example uses code from the following 3rd party projects: - Gson - Gson is a Java library that can be used to convert Java Objects into their JSON representation. (https://github.com/google/gson/blob/master/LICENSE) License: Apache 2.0 License File: gson.license @@ -13,6 +12,10 @@ Jackson Databind - General-purpose data-binding functionality and tree-model for License: Apache 2.0 License File: jackson-databind.license +Jackson Dataformat Properties - Support for reading and writing content of "Java Properties" style configuration files as if there was implied nesting structure (by default using dots as separators). (https://github.com/FasterXML/jackson-dataformats-text/blob/master/LICENSE) +License: Apache 2.0 +License File: jackson-dataformat-properties.license + jQuery.BlockUI - The jQuery BlockUI Plugin lets you simulate synchronous behavior when using AJAX, without locking the browser. (https://github.com/malsup/blockui/) License: MIT, GPL License File: jQuery.BlockUI.license @@ -37,6 +40,10 @@ jQuery.UI - jQuery UI is an open source library of interface components — License: MIT License File: jQuery.UI.license +JSON - JSON is a light-weight, language independent, data interchange format. (https://github.com/stleary/JSON-java/blob/master/LICENSE) +License Public Domain +License File: json.license + JSON.simple - JSON.simple is a simple Java toolkit for JSON. You can use JSON.simple to encode or decode JSON text. (https://github.com/fangyidong/json-simple/blob/master/LICENSE.txt) License: Apache 2.0 License File: JSON.simple.license @@ -49,11 +56,11 @@ ModelMapper - ModelMapper is an intelligent object mapping library that automa License: Apache 2.0 License File modelmapper.license -Prime JWT - is intended to be fast and easy to use. Prime JWT has a single external dependency on Jackson. (https://github.com/ws-apps/prime-jwt/blob/master/LICENSE) +Spring Boot - Helps create Spring-powered, production-grade applications and services. Has external dependencies on Spring Framework. (https://github.com/spring-projects/spring-boot/blob/main/LICENSE.txt) License: Apache 2.0 -License File: prime-jwt.license +License File: spring-boot.license -Spring Boot - Helps create Spring-powered, production-grade applications and services. Has external dependencies on Spring Framework. (https://github.com/spring-projects/spring-boot/blob/main/LICENSE.txt) +Spring Boot Web - Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container. (https://github.com/spring-projects/spring-boot/blob/main/LICENSE.txt) License: Apache 2.0 License File: spring-boot.license diff --git a/web/documentserver-example/java-spring/README.md b/web/documentserver-example/java-spring/README.md index a6cb879a9..f8e7370fa 100755 --- a/web/documentserver-example/java-spring/README.md +++ b/web/documentserver-example/java-spring/README.md @@ -135,9 +135,9 @@ See the detailed guide to learn how to install Document Server [for Linux](https a) archive with Java-Spring: ``` - wget https://api.onlyoffice.com/app_data/editor/Java.Spring.Example.zip + wget https://github.com/ONLYOFFICE/document-server-integration/releases/latest/download/Java.Spring.Example.zip ``` - + ``` unzip Java.Spring.Example.zip ``` diff --git a/web/documentserver-example/java-spring/licenses/3rd-Party.license b/web/documentserver-example/java-spring/licenses/3rd-Party.license index bd056534b..ccb8e57ec 100644 --- a/web/documentserver-example/java-spring/licenses/3rd-Party.license +++ b/web/documentserver-example/java-spring/licenses/3rd-Party.license @@ -1,6 +1,5 @@ ONLYOFFICE Applications example uses code from the following 3rd party projects: - Gson - Gson is a Java library that can be used to convert Java Objects into their JSON representation. (https://github.com/google/gson/blob/master/LICENSE) License: Apache 2.0 License File: gson.license @@ -13,6 +12,10 @@ Jackson Databind - General-purpose data-binding functionality and tree-model for License: Apache 2.0 License File: jackson-databind.license +Jackson Dataformat Properties - Support for reading and writing content of "Java Properties" style configuration files as if there was implied nesting structure (by default using dots as separators). (https://github.com/FasterXML/jackson-dataformats-text/blob/master/LICENSE) +License: Apache 2.0 +License File: jackson-dataformat-properties.license + jQuery.BlockUI - The jQuery BlockUI Plugin lets you simulate synchronous behavior when using AJAX, without locking the browser. (https://github.com/malsup/blockui/) License: MIT, GPL License File: jQuery.BlockUI.license @@ -37,6 +40,10 @@ jQuery.UI - jQuery UI is an open source library of interface components — License: MIT License File: jQuery.UI.license +JSON - JSON is a light-weight, language independent, data interchange format. (https://github.com/stleary/JSON-java/blob/master/LICENSE) +License Public Domain +License File: json.license + JSON.simple - JSON.simple is a simple Java toolkit for JSON. You can use JSON.simple to encode or decode JSON text. (https://github.com/fangyidong/json-simple/blob/master/LICENSE.txt) License: Apache 2.0 License File: JSON.simple.license @@ -49,11 +56,11 @@ ModelMapper - ModelMapper is an intelligent object mapping library that automa License: Apache 2.0 License File modelmapper.license -Prime JWT - is intended to be fast and easy to use. Prime JWT has a single external dependency on Jackson. (https://github.com/ws-apps/prime-jwt/blob/master/LICENSE) +Spring Boot - Helps create Spring-powered, production-grade applications and services. Has external dependencies on Spring Framework. (https://github.com/spring-projects/spring-boot/blob/main/LICENSE.txt) License: Apache 2.0 -License File: prime-jwt.license +License File: spring-boot.license -Spring Boot - Helps create Spring-powered, production-grade applications and services. Has external dependencies on Spring Framework. (https://github.com/spring-projects/spring-boot/blob/main/LICENSE.txt) +Spring Boot Web - Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container. (https://github.com/spring-projects/spring-boot/blob/main/LICENSE.txt) License: Apache 2.0 License File: spring-boot.license diff --git a/web/documentserver-example/java-spring/licenses/prime-jwt.license b/web/documentserver-example/java-spring/licenses/jackson-databind-properties.license similarity index 99% rename from web/documentserver-example/java-spring/licenses/prime-jwt.license rename to web/documentserver-example/java-spring/licenses/jackson-databind-properties.license index ad410e113..7a4a3ea24 100644 --- a/web/documentserver-example/java-spring/licenses/prime-jwt.license +++ b/web/documentserver-example/java-spring/licenses/jackson-databind-properties.license @@ -1,4 +1,5 @@ -Apache License + + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -178,7 +179,7 @@ Apache License APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" + boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a @@ -186,7 +187,7 @@ Apache License same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/web/documentserver-example/java-spring/licenses/json.license b/web/documentserver-example/java-spring/licenses/json.license new file mode 100644 index 000000000..227f84cb5 --- /dev/null +++ b/web/documentserver-example/java-spring/licenses/json.license @@ -0,0 +1 @@ +Public Domain. \ No newline at end of file diff --git a/web/documentserver-example/java-spring/pom.xml b/web/documentserver-example/java-spring/pom.xml index ba4ac4d32..eb02f794a 100644 --- a/web/documentserver-example/java-spring/pom.xml +++ b/web/documentserver-example/java-spring/pom.xml @@ -50,11 +50,6 @@ gson 2.8.9 - - com.inversoft - prime-jwt - 1.3.1 - org.projectlombok lombok @@ -71,12 +66,39 @@ jackson-databind 2.13.4.2 + + com.fasterxml.jackson.dataformat + jackson-dataformat-properties + 2.13.5 + org.modelmapper modelmapper 2.4.2 + + org.json + json + 20231013 + + + com.onlyoffice + docs-integration-sdk + 1.2.1-SNAPSHOT + + + + + ossrh + Sonatype OSSRH + https://s01.oss.sonatype.org/content/repositories/snapshots/ + + true + + + + diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/ExampleData.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/ExampleData.java index 4f84f90be..9b6630ae8 100644 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/ExampleData.java +++ b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/ExampleData.java @@ -19,6 +19,7 @@ package com.onlyoffice.integration; import com.onlyoffice.integration.documentserver.serializers.FilterState; +import com.onlyoffice.integration.entities.Goback; import com.onlyoffice.integration.services.UserServices; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -92,30 +93,34 @@ public void init() { "Can create a file from an editor", "Can see the information about Group2 users", "Can view chat", - "Can’t submit forms" + "Can’t submit forms", + "Can't close history", + "Can't restore the file version" ); // create user 1 with the specified parameters userService.createUser("John Smith", "smith@example.com", descriptionUserFirst, "", List.of(FilterState.NULL.toString()), List.of(FilterState.NULL.toString()), List.of(FilterState.NULL.toString()), List.of(FilterState.NULL.toString()), - List.of(FilterState.NULL.toString()), null, true, true, true); + List.of(FilterState.NULL.toString()), null, true, true, true, + new Goback(null, false), true); // create user 2 with the specified parameters userService.createUser("Mark Pottato", "pottato@example.com", descriptionUserSecond, "group-2", List.of("", "group-2"), List.of(FilterState.NULL.toString()), List.of("group-2", ""), List.of("group-2"), List.of("group-2", ""), true, true, - true, true); + true, true, new Goback("Go to Documents", null), false); // create user 3 with the specified parameters userService.createUser("Hamish Mitchell", null, descriptionUserThird, "group-3", List.of("group-2"), List.of("group-2", "group-3"), List.of("group-2"), - new ArrayList<>(), List.of("group-2"), false, true, true, false); + new ArrayList<>(), List.of("group-2"), false, true, true, false, + null, false); // create user 0 with the specified parameters userService.createUser("Anonymous", null, descriptionUserZero, "", List.of(FilterState.NULL.toString()), List.of(FilterState.NULL.toString()), List.of(FilterState.NULL.toString()), List.of(FilterState.NULL.toString()), - new ArrayList<>(), null, false, false, false); + new ArrayList<>(), null, false, false, false, null, false); } } diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/IntegrationConfiguration.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/IntegrationConfiguration.java index a19120859..36fdde6ec 100644 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/IntegrationConfiguration.java +++ b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/IntegrationConfiguration.java @@ -20,6 +20,17 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.onlyoffice.integration.documentserver.storage.FileStoragePathBuilder; +import com.onlyoffice.manager.document.DocumentManager; +import com.onlyoffice.manager.request.DefaultRequestManager; +import com.onlyoffice.manager.request.RequestManager; +import com.onlyoffice.manager.security.DefaultJwtManager; +import com.onlyoffice.manager.security.JwtManager; +import com.onlyoffice.manager.settings.SettingsManager; +import com.onlyoffice.manager.url.UrlManager; +import com.onlyoffice.service.command.CommandService; +import com.onlyoffice.service.command.DefaultCommandService; +import com.onlyoffice.service.convert.ConvertService; +import com.onlyoffice.service.convert.DefaultConvertService; import org.json.simple.parser.JSONParser; import org.modelmapper.ModelMapper; import org.modelmapper.convention.MatchingStrategies; @@ -30,23 +41,15 @@ import javax.annotation.PostConstruct; -import com.onlyoffice.integration.documentserver.util.SSLUtils; - @Configuration public class IntegrationConfiguration { @Value("${files.storage}") private String storageAddress; - @Value("${files.docservice.verify-peer-off}") - private String verifyPerrOff; - @Autowired private FileStoragePathBuilder storagePathBuilder; - @Autowired - private SSLUtils ssl; - @Bean public ModelMapper mapper() { // create the model mapper ModelMapper mapper = new ModelMapper(); @@ -67,21 +70,34 @@ public JSONParser jsonParser() { // create JSON parser @PostConstruct public void init() { // initialize the storage path builder storagePathBuilder.configure(storageAddress.isBlank() ? null : storageAddress); - if (!verifyPerrOff.isEmpty()) { - try { - if (verifyPerrOff.equals("true")) { - ssl.turnOffSslChecking(); //the certificate will be ignored - } else { - ssl.turnOnSslChecking(); //the certificate will be verified - } - } catch (Exception e) { - e.printStackTrace(); - } - } } @Bean public ObjectMapper objectMapper() { // create the object mapper return new ObjectMapper(); } + + @Bean + public JwtManager jwtManager(final SettingsManager settingsManager) { + return new DefaultJwtManager(settingsManager); + } + + @Bean + public RequestManager requestManager(final UrlManager urlManager, final JwtManager jwtManager, + final SettingsManager settingsManager) { + return new DefaultRequestManager(urlManager, jwtManager, settingsManager); + } + + @Bean + public ConvertService convertService(final DocumentManager documentManager, final UrlManager urlManager, + final RequestManager requestManager, + final SettingsManager settingsManager) { + return new DefaultConvertService(documentManager, urlManager, requestManager, settingsManager); + } + + @Bean + public CommandService commandService(final RequestManager requestManager) { + return new DefaultCommandService(requestManager); + } + } diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/controllers/EditorController.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/controllers/EditorController.java index 6a4c34e48..f46c52dd5 100755 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/controllers/EditorController.java +++ b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/controllers/EditorController.java @@ -20,19 +20,21 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.onlyoffice.integration.documentserver.managers.jwt.JwtManager; import com.onlyoffice.integration.documentserver.models.enums.Action; import com.onlyoffice.integration.documentserver.storage.FileStoragePathBuilder; -import com.onlyoffice.integration.entities.User; import com.onlyoffice.integration.dto.Mentions; -import com.onlyoffice.integration.dto.UserInfo; import com.onlyoffice.integration.dto.Protect; -import com.onlyoffice.integration.documentserver.models.enums.Type; -import com.onlyoffice.integration.documentserver.models.filemodel.FileModel; +import com.onlyoffice.integration.dto.UserInfo; +import com.onlyoffice.integration.entities.User; +import com.onlyoffice.integration.sdk.manager.UrlManager; +import com.onlyoffice.integration.sdk.service.ConfigService; import com.onlyoffice.integration.services.UserServices; -import com.onlyoffice.integration.services.configurers.FileConfigurer; -import com.onlyoffice.integration.services.configurers.wrappers.DefaultFileWrapper; +import com.onlyoffice.manager.security.JwtManager; +import com.onlyoffice.manager.settings.SettingsManager; +import com.onlyoffice.model.documenteditor.Config; +import com.onlyoffice.model.documenteditor.config.document.Type; import lombok.SneakyThrows; +import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; @@ -56,12 +58,6 @@ @Controller public class EditorController { - @Value("${files.docservice.url.site}") - private String docserviceSite; - - @Value("${files.docservice.url.api}") - private String docserviceApiUrl; - @Value("${files.docservice.languages}") private String langs; @@ -78,7 +74,13 @@ public class EditorController { private ObjectMapper objectMapper; @Autowired - private FileConfigurer fileConfigurer; + private SettingsManager settingsManager; + + @Autowired + private ConfigService configService; + + @Autowired + private UrlManager urlManager; @GetMapping(path = "${url.editor}") // process request to open the editor page @@ -86,20 +88,18 @@ public String index(@RequestParam("fileName") final String fileName, @RequestParam(value = "action", required = false) final String actionParam, @RequestParam(value = "type", required = false) final String typeParam, @RequestParam(value = "actionLink", required = false) final String actionLink, - @RequestParam(value = "directUrl", required = false, - defaultValue = "false") final Boolean directUrl, @CookieValue(value = "uid") final String uid, @CookieValue(value = "ulang") final String lang, final Model model) throws JsonProcessingException { - Action action = Action.edit; - Type type = Type.desktop; + Action action = null; + Type type = Type.DESKTOP; Locale locale = new Locale("en"); if (actionParam != null) { action = Action.valueOf(actionParam); } if (typeParam != null) { - type = Type.valueOf(typeParam); + type = Type.valueOf(typeParam.toUpperCase()); } List langsAndKeys = Arrays.asList(langs.split("\\|")); @@ -118,39 +118,34 @@ public String index(@RequestParam("fileName") final String fileName, return "index.html"; } - User user = optionalUser.get(); - user.setImage(user.getAvatar() ? storagePathBuilder.getServerUrl(true) + "/css/img/uid-" - + user.getId() + ".png" : null); - - // get file model with the default file parameters - FileModel fileModel = fileConfigurer.getFileModel( - DefaultFileWrapper - .builder() - .fileName(fileName) - .type(type) - .lang(locale.toLanguageTag()) - .action(action) - .user(user) - .actionData(actionLink) - .isEnableDirectUrl(directUrl) - .build() + Config config = configService.createConfig( + fileName, + action, + type ); - // add attributes to the specified model - // add file model with the default parameters to the original model - model.addAttribute("model", fileModel); + JSONObject actionData = null; + + if (actionLink != null && !actionLink.isEmpty()) { + actionData = new JSONObject(actionLink); + } + + config.getEditorConfig().setActionLink(actionData); + config.getEditorConfig().setLang(locale.toLanguageTag()); + + model.addAttribute("model", config); // create the document service api URL and add it to the model - model.addAttribute("docserviceApiUrl", docserviceSite + docserviceApiUrl); + model.addAttribute("docserviceApiUrl", urlManager.getDocumentServerApiUrl()); // get an image and add it to the model - model.addAttribute("dataInsertImage", getInsertImage(directUrl)); + model.addAttribute("dataInsertImage", getInsertImage()); // get a document for comparison and add it to the model - model.addAttribute("dataDocument", getCompareFile(directUrl)); + model.addAttribute("dataDocument", getCompareFile()); // get recipients data for mail merging and add it to the model - model.addAttribute("dataSpreadsheet", getSpreadsheet(directUrl)); + model.addAttribute("dataSpreadsheet", getSpreadsheet()); // get user data for mentions and add it to the model model.addAttribute("usersForMentions", getUserMentions(uid)); @@ -210,17 +205,13 @@ private List getUserProtect(final String uid) { // get user data for p @SneakyThrows - private String getInsertImage(final Boolean directUrl) { // get an image that will be inserted into the document + private String getInsertImage() { // get an image that will be inserted into the document Map dataInsertImage = new HashMap<>(); - dataInsertImage.put("fileType", "png"); - dataInsertImage.put("url", storagePathBuilder.getServerUrl(true) + "/css/img/logo.png"); - if (directUrl) { - dataInsertImage.put("directUrl", storagePathBuilder - .getServerUrl(false) + "/css/img/logo.png"); - } + dataInsertImage.put("fileType", "svg"); + dataInsertImage.put("url", storagePathBuilder.getServerUrl(true) + "/css/img/logo.svg"); // check if the document token is enabled - if (jwtManager.tokenEnabled()) { + if (settingsManager.isSecurityEnabled()) { // create token from the dataInsertImage object dataInsertImage.put("token", jwtManager.createToken(dataInsertImage)); @@ -232,17 +223,13 @@ private String getInsertImage(final Boolean directUrl) { // get an image that w // get a document that will be compared with the current document @SneakyThrows - private String getCompareFile(final Boolean directUrl) { + private String getCompareFile() { Map dataDocument = new HashMap<>(); dataDocument.put("fileType", "docx"); dataDocument.put("url", storagePathBuilder.getServerUrl(true) + "/assets?name=sample.docx"); - if (directUrl) { - dataDocument.put("directUrl", storagePathBuilder - .getServerUrl(false) + "/assets?name=sample.docx"); - } // check if the document token is enabled - if (jwtManager.tokenEnabled()) { + if (settingsManager.isSecurityEnabled()) { // create token from the dataDocument object dataDocument.put("token", jwtManager.createToken(dataDocument)); @@ -252,16 +239,13 @@ private String getCompareFile(final Boolean directUrl) { } @SneakyThrows - private String getSpreadsheet(final Boolean directUrl) { + private String getSpreadsheet() { Map dataSpreadsheet = new HashMap<>(); // get recipients data for mail merging dataSpreadsheet.put("fileType", "csv"); dataSpreadsheet.put("url", storagePathBuilder.getServerUrl(true) + "/csv"); - if (directUrl) { - dataSpreadsheet.put("directUrl", storagePathBuilder.getServerUrl(false) + "/csv"); - } // check if the document token is enabled - if (jwtManager.tokenEnabled()) { + if (settingsManager.isSecurityEnabled()) { // create token from the dataSpreadsheet object dataSpreadsheet.put("token", jwtManager.createToken(dataSpreadsheet)); diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/controllers/FileController.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/controllers/FileController.java index 854ce1d7c..5d37cb5d5 100755 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/controllers/FileController.java +++ b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/controllers/FileController.java @@ -21,32 +21,38 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.onlyoffice.integration.documentserver.callbacks.CallbackHandler; import com.onlyoffice.integration.documentserver.managers.history.HistoryManager; -import com.onlyoffice.integration.documentserver.managers.jwt.JwtManager; import com.onlyoffice.integration.documentserver.storage.FileStorageMutator; import com.onlyoffice.integration.documentserver.storage.FileStoragePathBuilder; import com.onlyoffice.integration.dto.Converter; -import com.onlyoffice.integration.dto.ConvertedData; import com.onlyoffice.integration.dto.Reference; -import com.onlyoffice.integration.dto.ReferenceData; import com.onlyoffice.integration.dto.Rename; import com.onlyoffice.integration.dto.Restore; import com.onlyoffice.integration.dto.SaveAs; -import com.onlyoffice.integration.dto.Track; import com.onlyoffice.integration.entities.User; -import com.onlyoffice.integration.documentserver.models.enums.DocumentType; +import com.onlyoffice.integration.sdk.manager.DocumentManager; import com.onlyoffice.integration.services.UserServices; -import com.onlyoffice.integration.documentserver.util.file.FileUtility; -import com.onlyoffice.integration.documentserver.util.service.ServiceConverter; -import com.onlyoffice.integration.documentserver.managers.document.DocumentManager; -import com.onlyoffice.integration.documentserver.managers.callback.CallbackManager; +import com.onlyoffice.manager.request.RequestManager; +import com.onlyoffice.manager.security.JwtManager; +import com.onlyoffice.manager.settings.SettingsManager; +import com.onlyoffice.manager.url.UrlManager; +import com.onlyoffice.model.commandservice.CommandRequest; +import com.onlyoffice.model.commandservice.CommandResponse; +import com.onlyoffice.model.commandservice.commandrequest.Command; +import com.onlyoffice.model.commandservice.commandrequest.Meta; +import com.onlyoffice.model.convertservice.ConvertRequest; +import com.onlyoffice.model.convertservice.ConvertResponse; +import com.onlyoffice.model.documenteditor.Callback; +import com.onlyoffice.model.documenteditor.config.document.ReferenceData; +import com.onlyoffice.service.command.CommandService; +import com.onlyoffice.service.convert.ConvertService; +import com.onlyoffice.service.documenteditor.callback.CallbackService; +import org.apache.http.HttpEntity; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.http.HttpHeaders; @@ -74,7 +80,6 @@ import java.io.InputStream; import java.net.InetAddress; import java.net.MalformedURLException; -import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -91,22 +96,6 @@ @Controller public class FileController { - @Value("${files.docservice.header}") - private String documentJwtHeader; - - @Value("${filesize-max}") - private String filesizeMax; - - @Value("${files.docservice.url.site}") - private String docserviceUrlSite; - - @Value("${files.docservice.url.command}") - private String docserviceUrlCommand; - - @Autowired - private FileUtility fileUtility; - @Autowired - private DocumentManager documentManager; @Autowired private JwtManager jwtManager; @Autowired @@ -116,20 +105,29 @@ public class FileController { @Autowired private UserServices userService; @Autowired - private CallbackHandler callbackHandler; - @Autowired private ObjectMapper objectMapper; @Autowired - private ServiceConverter serviceConverter; + private HistoryManager historyManager; @Autowired - private CallbackManager callbackManager; + private DocumentManager documentManager; @Autowired - private HistoryManager historyManager; + private ConvertService convertService; + @Autowired + private RequestManager requestManager; + @Autowired + private SettingsManager settingsManager; + @Autowired + private CallbackService callbackService; + @Autowired + private CommandService commandService; + @Autowired + private UrlManager urlManager; // create user metadata private String createUserMetadata(final String uid, final String fullFileName) { Optional optionalUser = userService.findUserById(Integer.parseInt(uid)); // find a user by their ID - String documentType = fileUtility.getDocumentType(fullFileName).toString().toLowerCase(); // get document type + // get document type + String documentType = documentManager.getDocumentType(fullFileName).toString().toLowerCase(); if (optionalUser.isPresent()) { User user = optionalUser.get(); storageMutator.createMeta(fullFileName, // create meta information with the user ID and name specified @@ -196,17 +194,17 @@ public String upload(@RequestParam("file") final MultipartFile file, // upload @CookieValue("uid") final String uid) { try { String fullFileName = file.getOriginalFilename(); // get file name - String fileExtension = fileUtility.getFileExtension(fullFileName); // get file extension + String fileExtension = documentManager.getExtension(fullFileName); // get file extension long fileSize = file.getSize(); // get file size byte[] bytes = file.getBytes(); // get file in bytes // check if the file size exceeds the maximum file size or is less than 0 - if (fileUtility.getMaxFileSize() < fileSize || fileSize <= 0) { + if (documentManager.getMaxFileSize() < fileSize || fileSize <= 0) { return "{ \"error\": \"File size is incorrect\"}"; // if so, write an error message to the response } // check if file extension is supported by the editor - if (!fileUtility.getFileExts().contains(fileExtension)) { + if (documentManager.getDocumentType(fullFileName) == null) { // if not, write an error message to the response return "{ \"error\": \"File type is not supported\"}"; @@ -217,7 +215,7 @@ public String upload(@RequestParam("file") final MultipartFile file, // upload throw new IOException("Could not update a file"); // if the file cannot be updated, an error occurs } - fullFileName = fileUtility.getFileNameWithoutExtension(fileNamePath) + fullFileName = documentManager.getBaseName(fileNamePath) + "." + fileExtension; // get full file name return createUserMetadata(uid, fullFileName); // create user metadata and return it @@ -235,80 +233,87 @@ public String convert(@RequestBody final Converter body, // convert a file @CookieValue("uid") final String uid, @CookieValue("ulang") final String lang) { // get file name String fileName = body.getFileName(); - - // get URL for downloading a file with the specified name - String fileUri = documentManager.getDownloadUrl(fileName, true); - // get file password if it exists String filePass = body.getFilePass() != null ? body.getFilePass() : null; - - // get file extension - String fileExt = fileUtility.getFileExtension(fileName); - - // get document type (word, cell or slide) - DocumentType type = fileUtility.getDocumentType(fileName); - - // convert to .ooxml - String internalFileExt = "ooxml"; + // get an auto-conversion extension from the request body or set it to the ooxml extension + String conversionExtension = body.getFileExt() != null ? body.getFileExt() : "ooxml"; try { // check if the file with such an extension can be converted - if (fileUtility.getConvertExts().contains(fileExt)) { - String key = serviceConverter.generateRevisionId(fileUri); // generate document key - ConvertedData response = serviceConverter // get the URL to the converted file - .getConvertedData(fileUri, fileExt, internalFileExt, key, filePass, true, lang); - - String newFileUri = response.getUri(); - String newFileType = "." + response.getFileType(); - - if (newFileUri.isEmpty()) { - return "{ \"step\" : \"0\", \"filename\" : \"" + fileName + "\"}"; + if (documentManager.getDefaultConvertExtension(fileName) != null) { + ConvertRequest convertRequest = ConvertRequest.builder() + .password(filePass) + .outputtype(conversionExtension) + .region(lang) + .async(true) + .build(); + + ConvertResponse convertResponse = convertService.processConvert(convertRequest, fileName); + + if (convertResponse.getError() != null || convertResponse.getFileUrl() == null) { + return objectMapper.writeValueAsString(convertResponse); } + String newFileUri = convertResponse.getFileUrl(); + String newFileType = convertResponse.getFileType(); + /* get a file name of an internal file extension with an index if the file with such a name already exists */ - String nameWithInternalExt = fileUtility.getFileNameWithoutExtension(fileName) + newFileType; + final String oldFileName = fileName; + String nameWithInternalExt = documentManager.getBaseName(fileName) + "." + newFileType; String correctedName = documentManager.getCorrectName(nameWithInternalExt); - URL url = new URL(newFileUri); - java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection(); - InputStream stream = connection.getInputStream(); // get input stream of the converted file + fileName = requestManager.executeGetRequest(newFileUri, new RequestManager.Callback() { + public String doWork(final Object response) throws IOException { + InputStream stream = ((HttpEntity) response).getContent(); // get input stream of the converted + // file - if (stream == null) { - connection.disconnect(); - throw new RuntimeException("Input stream is null"); - } + if (stream == null) { + throw new RuntimeException("Input stream is null"); + } - // remove source file - storageMutator.deleteFile(fileName); - // create the converted file with input stream - storageMutator.createFile(Path.of(storagePathBuilder.getFileLocation(correctedName)), stream); - fileName = correctedName; + // remove source file + storageMutator.deleteFile(oldFileName); + + // create the converted file with input stream + storageMutator.createFile(Path.of(storagePathBuilder.getFileLocation(correctedName)), stream); + return correctedName; + } + }); } // create meta information about the converted file with the user ID and name specified return createUserMetadata(uid, fileName); } catch (Exception e) { e.printStackTrace(); + + // if the operation of file converting is unsuccessful, an error occurs + return "{ \"error\": \"" + e.getMessage() + "\"}"; } - // if the operation of file converting is unsuccessful, an error occurs - return "{ \"error\": \"" + "The file can't be converted.\"}"; } @PostMapping("/delete") @ResponseBody public String delete(@RequestBody final Converter body) { // delete a file try { - String fullFileName = fileUtility.getFileName(body.getFileName()); // get full file name + String filename = body.getFileName(); + boolean success = false; - // delete a file from the storage and return the status of this operation (true or false) - boolean fileSuccess = storageMutator.deleteFile(fullFileName); + if (filename != null) { + String fullFileName = documentManager.getDocumentName(filename); // get full file name - // delete file history and return the status of this operation (true or false) - boolean historySuccess = storageMutator.deleteFileHistory(fullFileName); + // delete a file from the storage and return the status of this operation (true or false) + boolean fileSuccess = storageMutator.deleteFile(fullFileName); - return "{ \"success\": \"" + (fileSuccess && historySuccess) + "\"}"; + // delete file history and return the status of this operation (true or false) + boolean historySuccess = storageMutator.deleteFileHistory(fullFileName); + success = fileSuccess && historySuccess; + } else { + // delete the user's folder and return the boolean status + success = storageMutator.deleteUserFolder(); + } + return "{ \"success\": \"" + (success) + "\"}"; } catch (Exception e) { // if the operation of file deleting is unsuccessful, an error occurs return "{ \"error\": \"" + e.getMessage() + "\"}"; @@ -322,13 +327,12 @@ public ResponseEntity downloadHistory(final HttpServletRequest request @RequestParam("file") final String file) { // history file try { // check if a token is enabled or not - if (jwtManager.tokenEnabled() && jwtManager.tokenUseForRequest()) { - String header = request.getHeader(documentJwtHeader == null // get the document JWT header - || documentJwtHeader.isEmpty() ? "Authorization" : documentJwtHeader); + if (settingsManager.isSecurityEnabled()) { + String header = request.getHeader(settingsManager.getSecurityHeader()); if (header != null && !header.isEmpty()) { String token = header .replace("Bearer ", ""); // token is the header without the Bearer prefix - jwtManager.readToken(token); // read the token + jwtManager.verify(token); // read the token } else { return null; } @@ -346,13 +350,12 @@ public ResponseEntity download(final HttpServletRequest request, // d final String userAddress) { try { // check if a token is enabled or not - if (jwtManager.tokenEnabled() && userAddress != null && jwtManager.tokenUseForRequest()) { - String header = request.getHeader(documentJwtHeader == null // get the document JWT header - || documentJwtHeader.isEmpty() ? "Authorization" : documentJwtHeader); + if (settingsManager.isSecurityEnabled() && userAddress != null) { + String header = request.getHeader(settingsManager.getSecurityHeader()); if (header != null && !header.isEmpty()) { String token = header .replace("Bearer ", ""); // token is the header without the Bearer prefix - jwtManager.readToken(token); // read the token + jwtManager.verify(token); // read the token } else { return null; } @@ -382,10 +385,10 @@ public String create(@RequestParam("fileExt") sampleData, uid, user.get().getName()); // create a demo document with the sample data - if (fileName.isBlank() || fileName == null) { + if (fileName == null || fileName.isBlank()) { throw new RuntimeException("You must have forgotten to add asset files"); } - return "redirect:editor?fileName=" + URLEncoder + return "redirect:editor?action=edit&fileName=" + URLEncoder .encode(fileName, StandardCharsets.UTF_8); // redirect the request } catch (Exception ex) { model.addAttribute("error", ex.getMessage()); @@ -418,28 +421,29 @@ public ArrayList> files(@RequestParam(value = "fileId", requ public String track(final HttpServletRequest request, // track file changes @RequestParam("fileName") final String fileName, @RequestParam("userAddress") final String userAddress, - @RequestBody final Track body) { - Track track; + @RequestBody final Callback body) { + Callback callback; try { String bodyString = objectMapper .writeValueAsString(body); // write the request body to the object mapper as a string - String header = request.getHeader(documentJwtHeader == null // get the request header - || documentJwtHeader.isEmpty() ? "Authorization" : documentJwtHeader); if (bodyString.isEmpty()) { // if the request body is empty, an error occurs throw new RuntimeException("{\"error\":1,\"message\":\"Request payload is empty\"}"); } - JSONObject bodyCheck = jwtManager.parseBody(bodyString, header); // parse the request body - track = objectMapper.readValue(bodyCheck.toJSONString(), Track.class); // read the request body + String authorizationHeader = request.getHeader(settingsManager.getSecurityHeader()); + callback = callbackService.verifyCallback(body, authorizationHeader); + + callbackService.processCallback(callback, fileName); } catch (Exception e) { - e.printStackTrace(); - return e.getMessage(); + String message = e.getMessage(); + if (!message.contains("\"error\":1")) { + e.printStackTrace(); + } + return message; } - int error = callbackHandler.handle(track, fileName); - - return "{\"error\":" + error + "}"; + return "{\"error\":\"0\"}"; } @PostMapping("/saveas") @@ -447,24 +451,30 @@ public String track(final HttpServletRequest request, // track file changes public String saveAs(@RequestBody final SaveAs body, @CookieValue("uid") final String uid) { try { String fileName = documentManager.getCorrectName(body.getTitle()); - String curExt = fileUtility.getFileExtension(fileName); - if (!fileUtility.getFileExts().contains(curExt)) { + if (documentManager.getDocumentType(fileName) == null) { return "{\"error\":\"File type is not supported\"}"; } - URL url = new URL(body.getUrl()); - java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection(); - InputStream stream = connection.getInputStream(); + String url = body.getUrl(); - if (Integer.parseInt(filesizeMax) < stream.available() || stream.available() <= 0) { - return "{\"error\":\"File size is incorrect\"}"; - } - storageMutator.createFile(Path.of(storagePathBuilder.getFileLocation(fileName)), stream); - createUserMetadata(uid, fileName); + url = urlManager.replaceToInnerDocumentServerUrl(url); + + return requestManager.executeGetRequest(url, new RequestManager.Callback() { + @Override + public String doWork(final Object response) throws Exception { + InputStream stream = ((HttpEntity) response).getContent(); + + if (documentManager.getMaxFileSize() < stream.available() || stream.available() <= 0) { + return "{\"error\":\"File size is incorrect\"}"; + } + storageMutator.createFile(Path.of(storagePathBuilder.getFileLocation(fileName)), stream); + createUserMetadata(uid, fileName); - return "{\"file\": \"" + fileName + "\"}"; - } catch (IOException e) { + return "{\"file\": \"" + fileName + "\"}"; + } + }); + } catch (Exception e) { e.printStackTrace(); return "{ \"error\" : 1, \"message\" : \"" + e.getMessage() + "\"}"; } @@ -473,14 +483,18 @@ public String saveAs(@RequestBody final SaveAs body, @CookieValue("uid") final S @PostMapping("/rename") @ResponseBody public String rename(@RequestBody final Rename body) { - String fileName = body.getFileName(); - - HashMap meta = new HashMap<>(); - meta.put("title", fileName + "." + body.getFileType()); + CommandRequest commandRequest = CommandRequest.builder() + .key(body.getFileKey()) + .c(Command.META) + .meta(Meta.builder() + .title(body.getFileName() + "." + body.getFileType()) + .build()) + .build(); try { - callbackManager.commandRequest("meta", body.getFileKey(), meta); - return "result ok"; + + CommandResponse commandResponse = commandService.processCommand(commandRequest, body.getFileName()); + return commandResponse.getError().getDescription(); } catch (Exception e) { e.printStackTrace(); return e.getMessage(); @@ -514,7 +528,6 @@ public String reference(@RequestBody final Reference body) { if (!link.contains(storagePathBuilder.getServerUrl(true))) { HashMap data = new HashMap<>(); data.put("url", link); - data.put("directUrl", link); return gson.toJson(data); } @@ -529,7 +542,8 @@ public String reference(@RequestBody final Reference body) { if (fileName.equals("")) { try { String path = (String) body.getPath(); - path = fileUtility.getFileName(path); + path = path.substring(path.lastIndexOf('/') + 1); + path = path.split("\\?")[0]; File f = new File(storagePathBuilder.getFileLocation(path)); if (f.exists()) { fileName = path; @@ -552,19 +566,18 @@ public String reference(@RequestBody final Reference body) { referenceData.put("fileKey", gson.toJson(fileKey)); HashMap data = new HashMap<>(); - data.put("fileType", fileUtility.getFileExtension(fileName)); - data.put("key", serviceConverter.generateRevisionId( + data.put("fileType", documentManager.getDocumentName(fileName)); + data.put("key", documentManager.generateRevisionId( storagePathBuilder.getStorageLocation() + "/" + fileName + "/" + new File(storagePathBuilder.getFileLocation(fileName)).lastModified() )); - data.put("url", documentManager.getDownloadUrl(fileName, true)); - data.put("directUrl", body.getDirectUrl() ? documentManager.getDownloadUrl(fileName, false) : null); + data.put("url", urlManager.getFileUrl(fileName)); data.put("referenceData", referenceData); data.put("path", fileName); data.put("link", storagePathBuilder.getServerUrl(true) + "/editor?fileName=" + fileName); - if (jwtManager.tokenEnabled()) { + if (settingsManager.isSecurityEnabled()) { String token = jwtManager.createToken(data); data.put("token", token); } @@ -584,9 +597,8 @@ public String history(@RequestParam("fileName") final String fileName) { @GetMapping("/historydata") @ResponseBody public String history(@RequestParam("fileName") final String fileName, - @RequestParam("version") final String version, - @RequestParam(value = "directUrl", defaultValue = "false") final Boolean directUrl) { - return historyManager.getHistoryData(fileName, version, directUrl); + @RequestParam("version") final String version) { + return historyManager.getHistoryData(fileName, version); } @PutMapping("/restore") @@ -599,7 +611,7 @@ public String restore(@RequestBody final Restore body, @CookieValue("uid") final String historyDirectory = storagePathBuilder.getHistoryDir(sourcePathFile.toString()); Integer bumpedVersion = storagePathBuilder.getFileVersion(historyDirectory, false); - String bumpedVersionStringDirectory = documentManager.versionDir(historyDirectory, bumpedVersion, true); + String bumpedVersionStringDirectory = historyManager.versionDir(historyDirectory, bumpedVersion, true); File bumpedVersionDirectory = new File(bumpedVersionStringDirectory); if (!bumpedVersionDirectory.exists()) { bumpedVersionDirectory.mkdir(); @@ -608,7 +620,7 @@ public String restore(@RequestBody final Restore body, @CookieValue("uid") final Path bumpedKeyPathFile = Paths.get(bumpedVersionStringDirectory, "key.txt"); String bumpedKeyStringFile = bumpedKeyPathFile.toString(); File bumpedKeyFile = new File(bumpedKeyStringFile); - String bumpedKey = serviceConverter.generateRevisionId( + String bumpedKey = documentManager.generateRevisionId( storagePathBuilder.getStorageLocation() + "/" + body.getFileName() @@ -642,13 +654,13 @@ public String restore(@RequestBody final Restore body, @CookieValue("uid") final bumpedChangesFileWriter.write(bumpedChangesContent); bumpedChangesFileWriter.close(); - String sourceExtension = fileUtility.getFileExtension(body.getFileName()); + String sourceExtension = documentManager.getExtension(body.getFileName()); String previousBasename = "prev." + sourceExtension; Path bumpedFile = Paths.get(bumpedVersionStringDirectory, previousBasename); Files.move(sourcePathFile, bumpedFile); - String recoveryVersionStringDirectory = documentManager.versionDir( + String recoveryVersionStringDirectory = historyManager.versionDir( historyDirectory, body.getVersion(), true diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/controllers/ForgottenController.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/controllers/ForgottenController.java new file mode 100755 index 000000000..09a3346d2 --- /dev/null +++ b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/controllers/ForgottenController.java @@ -0,0 +1,121 @@ +/** + * + * (c) Copyright Ascensio System SIA 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.onlyoffice.integration.controllers; + +import com.onlyoffice.integration.dto.ForgottenFile; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.http.ResponseEntity; +import org.springframework.http.HttpStatus; + +import com.onlyoffice.model.commandservice.CommandRequest; +import com.onlyoffice.model.commandservice.CommandResponse; +import com.onlyoffice.model.commandservice.commandrequest.Command; +import com.onlyoffice.manager.document.DocumentManager; +import com.onlyoffice.service.command.CommandService; + +import java.util.ArrayList; +import java.util.List; + + +@CrossOrigin("*") +@Controller +public class ForgottenController { + @Value("${server.version}") + private String serverVersion; + + @Value("${enable-forgotten}") + private String enableForgotten; + + @Autowired + private CommandService commandService; + + @Autowired + private DocumentManager documentManager; + + @GetMapping("${url.forgotten}") + public String index(final Model model) { + if (!forgottenEnabled()) { + model.addAttribute("error", "The forgotten page is disabled"); + return "error.html"; + } + + model.addAttribute("files", getForgottenFiles()); + model.addAttribute("serverVersion", serverVersion); + + return "forgotten.html"; + } + + private ArrayList getForgottenFiles() { + ArrayList files = new ArrayList(); + try { + CommandRequest commandRequest = CommandRequest.builder() + .c(Command.GET_FORGOTTEN_LIST) + .build(); + CommandResponse commandResponse = commandService.processCommand(commandRequest, null); + List keys = commandResponse.getKeys(); + for (int i = 0; i < keys.size(); i++) { + commandRequest = CommandRequest.builder() + .c(Command.GET_FORGOTTEN) + .key(keys.get(i)) + .build(); + commandResponse = commandService.processCommand(commandRequest, null); + ForgottenFile file = new ForgottenFile( + commandResponse.getKey(), + documentManager.getDocumentType(commandResponse.getUrl()).toString().toLowerCase(), + commandResponse.getUrl() + ); + files.add(file); + } + } catch (Exception e) { + e.printStackTrace(); + } + return files; + } + + private boolean forgottenEnabled() { + return Boolean.valueOf(enableForgotten); + } + + @DeleteMapping("/forgotten/{filename}") + public ResponseEntity delete(@PathVariable("filename") final String filename) { + if (!forgottenEnabled()) { + return new ResponseEntity<>(HttpStatus.FORBIDDEN); + } + + try { + CommandRequest commandRequest = CommandRequest.builder() + .c(Command.DELETE_FORGOTTEN) + .key(filename) + .build(); + + CommandResponse commandResponse = commandService.processCommand(commandRequest, null); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } catch (Exception e) { + e.printStackTrace(); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/controllers/IndexController.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/controllers/IndexController.java index 6de1d9d29..e5e7d888d 100755 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/controllers/IndexController.java +++ b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/controllers/IndexController.java @@ -21,9 +21,11 @@ import com.onlyoffice.integration.documentserver.storage.FileStorageMutator; import com.onlyoffice.integration.documentserver.storage.FileStoragePathBuilder; import com.onlyoffice.integration.documentserver.util.Misc; -import com.onlyoffice.integration.documentserver.util.file.FileUtility; import com.onlyoffice.integration.entities.User; +import com.onlyoffice.integration.sdk.manager.DocumentManager; +import com.onlyoffice.integration.sdk.manager.UrlManager; import com.onlyoffice.integration.services.UserServices; +import com.onlyoffice.integration.dto.FormatsList; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; @@ -31,8 +33,8 @@ import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.http.ResponseEntity; import java.util.ArrayList; import java.util.Arrays; @@ -52,20 +54,17 @@ public class IndexController { @Autowired private FileStoragePathBuilder storagePathBuilder; - @Autowired - private FileUtility fileUtility; - @Autowired private Misc mistUtility; @Autowired private UserServices userService; - @Value("${files.docservice.url.site}") - private String docserviceSite; + @Autowired + private DocumentManager documentManager; - @Value("${files.docservice.url.preloader}") - private String docservicePreloader; + @Autowired + private UrlManager urlManager; @Value("${url.converter}") private String urlConverter; @@ -79,9 +78,11 @@ public class IndexController { @Value("${server.version}") private String serverVersion; + @Value("${enable-forgotten}") + private String enableForgotten; + @GetMapping("${url.index}") - public String index(@RequestParam(value = "directUrl", required = false) final Boolean directUrl, - final Model model) { + public String index(final Model model) { java.io.File[] files = storageMutator.getStoredFiles(); // get all the stored files from the storage List docTypes = new ArrayList<>(); List filesEditable = new ArrayList<>(); @@ -105,15 +106,14 @@ public String index(@RequestParam(value = "directUrl", required = false) final B for (java.io.File file:files) { // run through all the files String fileName = file.getName(); // get file name - docTypes.add(fileUtility + docTypes.add(documentManager .getDocumentType(fileName) .toString() .toLowerCase()); // add a document type of each file to the list - filesEditable.add(fileUtility.getEditedExts() - .contains(fileUtility.getFileExtension(fileName))); // specify if a file is editable or not + filesEditable.add(documentManager.isEditable(fileName)); // specify if a file is editable or not versions.add(" [" + storagePathBuilder. getFileVersion(fileName, true) + "]"); // add a file version to the list - isFillFormDoc.add(fileUtility.getFillExts().contains(fileUtility.getFileExtension(fileName))); + isFillFormDoc.add(documentManager.isFillable(fileName)); } // add all the parameters to the model @@ -122,12 +122,12 @@ public String index(@RequestParam(value = "directUrl", required = false) final B model.addAttribute("files", files); model.addAttribute("docTypes", docTypes); model.addAttribute("filesEditable", filesEditable); - model.addAttribute("datadocs", docserviceSite + docservicePreloader); + model.addAttribute("datadocs", urlManager.getDocumentServerPreloaderApiUrl()); model.addAttribute("tooltip", tooltip); model.addAttribute("users", users); model.addAttribute("languages", languages); - model.addAttribute("directUrl", directUrl); model.addAttribute("serverVersion", serverVersion); + model.addAttribute("enableForgotten", Boolean.valueOf(enableForgotten)); return "index.html"; } @@ -136,16 +136,16 @@ public String index(@RequestParam(value = "directUrl", required = false) final B @ResponseBody public HashMap configParameters() { // get configuration parameters HashMap configuration = new HashMap<>(); - - configuration.put("FillExtList", String.join(",", fileUtility - .getFillExts())); // put a list of the extensions that can be filled to config - configuration.put("ConverExtList", String.join(",", fileUtility - .getConvertExts())); // put a list of the extensions that can be converted to config - configuration.put("EditedExtList", String.join(",", fileUtility - .getEditedExts())); // put a list of the extensions that can be edited to config configuration.put("UrlConverter", urlConverter); configuration.put("UrlEditor", urlEditor); return configuration; } + + @GetMapping("/formats") + @ResponseBody + public ResponseEntity formats() { // return all the supported formats + FormatsList list = new FormatsList(documentManager.getFormats()); + return ResponseEntity.ok(list); + } } diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/Callback.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/Callback.java deleted file mode 100644 index 2c5d6b65a..000000000 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/Callback.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * - * (c) Copyright Ascensio System SIA 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.onlyoffice.integration.documentserver.callbacks; - -import com.onlyoffice.integration.dto.Track; -import org.springframework.beans.factory.annotation.Autowired; - -// specify the callback handler functions -public interface Callback { - int handle(Track body, String fileName); // handle the callback - int getStatus(); // get document status - @Autowired - default void selfRegistration(CallbackHandler callbackHandler) { // register a callback handler - callbackHandler.register(getStatus(), this); - } -} diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/CallbackHandler.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/CallbackHandler.java deleted file mode 100644 index a773d8e2d..000000000 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/CallbackHandler.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * - * (c) Copyright Ascensio System SIA 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.onlyoffice.integration.documentserver.callbacks; - -import com.onlyoffice.integration.dto.Track; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Service; - -import java.util.HashMap; -import java.util.Map; - -@Service -public class CallbackHandler { - - private Logger logger = LoggerFactory.getLogger(CallbackHandler.class); - - private Map callbackHandlers = new HashMap<>(); - - public void register(final int code, final Callback callback) { // register a callback handler - callbackHandlers.put(code, callback); - } - - public int handle(final Track body, final String fileName) { // handle a callback - Callback callback = callbackHandlers.get(body.getStatus()); - if (callback == null) { - logger.warn("Callback status " + body.getStatus() + " is not supported yet"); - return 0; - } - - int result = callback.handle(body, fileName); - return result; - } -} diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/Status.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/Status.java deleted file mode 100644 index 6de4d8feb..000000000 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/Status.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * - * (c) Copyright Ascensio System SIA 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.onlyoffice.integration.documentserver.callbacks; - -// document status -public enum Status { - EDITING(1), // 1 - document is being edited - SAVE(2), // 2 - document is ready for saving - CORRUPTED(3), // 3 - document saving error has occurred - MUST_FORCE_SAVE(6), // 6 - document is being edited, but the current document state is saved - CORRUPTED_FORCE_SAVE(7); // 7 - error has occurred while force saving the document - private int code; - Status(final int codeParam) { - this.code = codeParam; - } - public int getCode() { // get document status - return this.code; - } -} diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/implementations/EditCallback.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/implementations/EditCallback.java deleted file mode 100644 index 126778cfa..000000000 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/implementations/EditCallback.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * - * (c) Copyright Ascensio System SIA 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.onlyoffice.integration.documentserver.callbacks.implementations; - -import com.onlyoffice.integration.documentserver.callbacks.Callback; -import com.onlyoffice.integration.documentserver.callbacks.Status; -import com.onlyoffice.integration.documentserver.managers.callback.CallbackManager; -import com.onlyoffice.integration.dto.Action; -import com.onlyoffice.integration.dto.Track; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -public class EditCallback implements Callback { - @Autowired - private CallbackManager callbackManager; - @Override - public int handle(final Track body, - final String fileName) { // handle the callback when the document is being edited - int result = 0; - Action action = body.getActions().get(0); // get the user ID who is editing the document - if (action.getType().equals(com.onlyoffice.integration.documentserver.models.enums - .Action.edit)) { // if this value is not equal to the user ID - String user = action.getUserid(); // get user ID - if (!body.getUsers().contains(user)) { // if this user is not specified in the body - String key = body.getKey(); // get document key - try { - // create a command request to forcibly save the document being edited without closing it - callbackManager.commandRequest("forcesave", key, null); - } catch (Exception e) { - e.printStackTrace(); - result = 1; - } - } - } - return result; - } - - @Override - public int getStatus() { // get document status - return Status.EDITING.getCode(); // return status 1 - document is being edited - } -} diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/implementations/ForcesaveCallback.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/implementations/ForcesaveCallback.java deleted file mode 100644 index 8543361aa..000000000 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/implementations/ForcesaveCallback.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * - * (c) Copyright Ascensio System SIA 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.onlyoffice.integration.documentserver.callbacks.implementations; - -import com.onlyoffice.integration.documentserver.callbacks.Callback; -import com.onlyoffice.integration.documentserver.callbacks.Status; -import com.onlyoffice.integration.documentserver.managers.callback.CallbackManager; -import com.onlyoffice.integration.dto.Track; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -public class ForcesaveCallback implements Callback { - @Autowired - private CallbackManager callbackManager; - @Override - public int handle(final Track body, - final String fileName) { // handle the callback when the force saving request is performed - int result = 0; - try { - callbackManager.processForceSave(body, fileName); // file force saving process - } catch (Exception ex) { - ex.printStackTrace(); - result = 1; - } - return result; - } - - @Override - public int getStatus() { // get document status - // return status 6 - document is being edited, but the current document state is saved - return Status.MUST_FORCE_SAVE.getCode(); - } -} diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/implementations/SaveCallback.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/implementations/SaveCallback.java deleted file mode 100644 index 3448c6600..000000000 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/callbacks/implementations/SaveCallback.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * - * (c) Copyright Ascensio System SIA 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.onlyoffice.integration.documentserver.callbacks.implementations; - -import com.onlyoffice.integration.documentserver.callbacks.Callback; -import com.onlyoffice.integration.documentserver.callbacks.Status; -import com.onlyoffice.integration.documentserver.managers.callback.CallbackManager; -import com.onlyoffice.integration.dto.Track; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -public class SaveCallback implements Callback { - @Autowired - private CallbackManager callbackManager; - @Override - public int handle(final Track body, - final String fileName) { // handle the callback when the saving request is performed - int result = 0; - try { - callbackManager.processSave(body, fileName); // file saving process - } catch (Exception ex) { - ex.printStackTrace(); - result = 1; - } - - return result; - } - - @Override - public int getStatus() { // get document status - return Status.SAVE.getCode(); // return status 2 - document is ready for saving - } -} diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/callback/CallbackManager.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/callback/CallbackManager.java index 8ef839282..746954b24 100644 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/callback/CallbackManager.java +++ b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/callback/CallbackManager.java @@ -18,11 +18,10 @@ package com.onlyoffice.integration.documentserver.managers.callback; -import com.onlyoffice.integration.dto.Track; -import java.util.HashMap; +import com.onlyoffice.model.documenteditor.Callback; public interface CallbackManager { // specify the callback manager functions - void processSave(Track body, String fileName); // file saving process - void commandRequest(String method, String key, HashMap meta); // create a command request - void processForceSave(Track body, String fileName); // file force saving process + void processEditing(Callback callback, String fileName); + void processSave(Callback callback, String fileName); + void processForceSave(Callback callback, String fileName); } diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/callback/DefaultCallbackManager.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/callback/DefaultCallbackManager.java index 8323b4136..8613cffbb 100755 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/callback/DefaultCallbackManager.java +++ b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/callback/DefaultCallbackManager.java @@ -19,62 +19,60 @@ package com.onlyoffice.integration.documentserver.managers.callback; import com.fasterxml.jackson.databind.ObjectMapper; -import com.onlyoffice.integration.documentserver.managers.document.DocumentManager; -import com.onlyoffice.integration.documentserver.managers.jwt.JwtManager; +import com.onlyoffice.integration.documentserver.managers.history.HistoryManager; import com.onlyoffice.integration.documentserver.storage.FileStorageMutator; import com.onlyoffice.integration.documentserver.storage.FileStoragePathBuilder; -import com.onlyoffice.integration.documentserver.util.file.FileUtility; -import com.onlyoffice.integration.dto.Action; -import com.onlyoffice.integration.documentserver.util.service.ServiceConverter; -import com.onlyoffice.integration.dto.Track; +import com.onlyoffice.integration.sdk.manager.DocumentManager; +import com.onlyoffice.integration.sdk.manager.UrlManager; +import com.onlyoffice.manager.request.RequestManager; +import com.onlyoffice.model.commandservice.CommandRequest; +import com.onlyoffice.model.commandservice.commandrequest.Command; +import com.onlyoffice.model.convertservice.ConvertRequest; +import com.onlyoffice.model.convertservice.ConvertResponse; +import com.onlyoffice.model.documenteditor.Callback; +import com.onlyoffice.model.documenteditor.callback.Action; +import com.onlyoffice.model.documenteditor.callback.ForcesaveType; +import com.onlyoffice.model.documenteditor.callback.action.Type; +import com.onlyoffice.service.command.CommandService; +import com.onlyoffice.service.convert.ConvertService; import lombok.SneakyThrows; +import org.apache.http.HttpEntity; import org.json.simple.JSONObject; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Primary; -import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import java.io.ByteArrayInputStream; import java.io.File; +import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; -import java.net.URL; -import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.HashMap; import java.util.List; -import java.util.Map; - -import static com.onlyoffice.integration.documentserver.util.Constants.FILE_SAVE_TIMEOUT; - // todo: Refactoring @Component @Primary public class DefaultCallbackManager implements CallbackManager { - @Value("${files.docservice.url.site}") - private String docserviceUrlSite; - @Value("${files.docservice.url.command}") - private String docserviceUrlCommand; - @Value("${files.docservice.header}") - private String documentJwtHeader; - @Autowired private DocumentManager documentManager; @Autowired - private JwtManager jwtManager; - @Autowired - private FileUtility fileUtility; - @Autowired private FileStorageMutator storageMutator; @Autowired private FileStoragePathBuilder storagePathBuilder; @Autowired private ObjectMapper objectMapper; @Autowired - private ServiceConverter serviceConverter; + private ConvertService convertService; + @Autowired + private RequestManager requestManager; + @Autowired + private CommandService commandService; + @Autowired + private HistoryManager historyManager; + + @Autowired + private UrlManager urlManager; // download file from url @SneakyThrows @@ -83,23 +81,18 @@ private byte[] getDownloadFile(final String url) { throw new RuntimeException("Url argument is not specified"); // URL isn't specified } - URL uri = new URL(url); - java.net.HttpURLConnection connection = (java.net.HttpURLConnection) uri.openConnection(); - connection.setConnectTimeout(FILE_SAVE_TIMEOUT); - InputStream stream = connection.getInputStream(); // get input stream of the file information from the URL + return requestManager.executeGetRequest(url, new RequestManager.Callback() { + public byte[] doWork(final Object response) throws IOException { + InputStream stream = ((HttpEntity) response).getContent(); // get input stream of the converted + // file - int statusCode = connection.getResponseCode(); - if (statusCode != HttpStatus.OK.value()) { // checking status code - connection.disconnect(); - throw new RuntimeException("Document editing service returned status: " + statusCode); - } - - if (stream == null) { - connection.disconnect(); - throw new RuntimeException("Input stream is null"); - } + if (stream == null) { + throw new RuntimeException("Input stream is null"); + } - return stream.readAllBytes(); + return stream.readAllBytes(); + } + }); } // file saving @@ -112,40 +105,76 @@ private void saveFile(final byte[] byteArray, final Path path) { storageMutator.createOrUpdateFile(path, new ByteArrayInputStream(byteArray)); } + @Override + public void processEditing(final Callback callback, final String fileName) { + Action action = callback.getActions().get(0); // get the user ID who is editing the document + if (action.getType().equals(Type.CONNECTED)) { // if this value is not equal to the user ID + String user = action.getUserid(); // get user ID + if (!callback.getUsers().contains(user)) { // if this user is not specified in the body + CommandRequest commandRequest = CommandRequest.builder() + .c(Command.FORCESAVE) + .key(callback.getKey()) + .build(); + + // create a command request to forcibly save the document being edited without closing it + try { + commandService.processCommand(commandRequest, fileName); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + } + @SneakyThrows - public void processSave(final Track body, final String fileName) { // file saving process - String downloadUri = body.getUrl(); - String changesUri = body.getChangesurl(); - String key = body.getKey(); + public void processSave(final Callback callback, final String fileName) { // file saving process + String downloadUri = callback.getUrl(); + String changesUri = callback.getChangesurl(); + String key = callback.getKey(); String newFileName = fileName; - String curExt = fileUtility.getFileExtension(fileName); // get current file extension - String downloadExt = body.getFiletype(); // get an extension of the downloaded file + String curExt = documentManager.getExtension(fileName); // get current file extension + String downloadExt = callback.getFiletype(); // get an extension of the downloaded file + String storagePath = storagePathBuilder.getFileLocation(newFileName); // get the path to a new file + + if (!Paths.get(storagePath).toFile().exists()) { + throw new RuntimeException("{\"error\":1, \"message\":\"file does not exist\"}"); + } + + downloadUri = urlManager.replaceToInnerDocumentServerUrl(downloadUri); + changesUri = urlManager.replaceToInnerDocumentServerUrl(changesUri); // todo: Refactoring // convert downloaded file to the file with the current extension if these extensions aren't equal if (!curExt.equals(downloadExt)) { try { - String newFileUri = serviceConverter - .getConvertedData(downloadUri, downloadExt, curExt, - serviceConverter.generateRevisionId(downloadUri), null, false, - null).getUri(); // convert a file and get URL to a new file - if (newFileUri.isEmpty()) { + ConvertRequest convertRequest = ConvertRequest.builder() + .key(documentManager.generateRevisionId(downloadUri)) + .url(downloadUri) + .outputtype(curExt) + .async(false) + .build(); + + // convert a file and get URL to a new file + ConvertResponse convertResponse = convertService.processConvert(convertRequest, fileName); + + String newFileUri = convertResponse.getFileUrl(); + + if (newFileUri == null || newFileUri.isEmpty()) { newFileName = documentManager - .getCorrectName(fileUtility.getFileNameWithoutExtension(fileName) + "." + .getCorrectName(documentManager.getBaseName(fileName) + "." + downloadExt); // get the correct file name if it already exists } else { downloadUri = newFileUri; } } catch (Exception e) { newFileName = documentManager - .getCorrectName(fileUtility.getFileNameWithoutExtension(fileName) + "." + downloadExt); + .getCorrectName(documentManager.getBaseName(fileName) + "." + downloadExt); } } byte[] byteArrayFile = getDownloadFile(downloadUri); // download document file - String storagePath = storagePathBuilder.getFileLocation(newFileName); // get the path to a new file Path lastVersion = Paths.get(storagePathBuilder .getFileLocation(fileName)); // get the path to the last file version @@ -153,7 +182,7 @@ public void processSave(final Track body, final String fileName) { // file savi Path histDir = Paths.get(storagePathBuilder.getHistoryDir(storagePath)); // get the history directory storageMutator.createDirectory(histDir); // and create it - String versionDir = documentManager + String versionDir = historyManager .versionDir(histDir.toAbsolutePath().toString(), // get the file version directory storagePathBuilder .getFileVersion(histDir.toAbsolutePath().toString(), false), true); @@ -172,13 +201,13 @@ public void processSave(final Track body, final String fileName) { // file savi .of(versionDir + File.separator + "diff.zip")); // save file changes to the diff.zip archive JSONObject jsonChanges = new JSONObject(); // create a json object for document changes - jsonChanges.put("changes", body.getHistory().getChanges()); // put the changes to the json object - jsonChanges.put("serverVersion", body.getHistory() + jsonChanges.put("changes", callback.getHistory().getChanges()); // put the changes to the json object + jsonChanges.put("serverVersion", callback.getHistory() .getServerVersion()); // put the server version to the json object String history = objectMapper.writeValueAsString(jsonChanges); - if (history == null && body.getHistory() != null) { - history = objectMapper.writeValueAsString(body.getHistory()); + if (history == null && callback.getHistory() != null) { + history = objectMapper.writeValueAsString(callback.getHistory()); } if (history != null && !history.isEmpty()) { @@ -194,82 +223,16 @@ public void processSave(final Track body, final String fileName) { // file savi } } - // todo: Replace (String method) with (Enum method) @SneakyThrows - public void commandRequest(final String method, - final String key, - final HashMap meta) { // create a command request - String documentCommandUrl = docserviceUrlSite + docserviceUrlCommand; - - URL url = new URL(documentCommandUrl); - java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection(); - - HashMap params = new HashMap(); - params.put("c", method); - params.put("key", key); - - if (meta != null) { - params.put("meta", meta); - } - - String headerToken; - // check if a secret key to generate token exists or not - if (jwtManager.tokenEnabled() && jwtManager.tokenUseForRequest()) { - Map payloadMap = new HashMap<>(); - payloadMap.put("payload", params); - headerToken = jwtManager.createToken(payloadMap); // encode a payload object into a header token - - // add a header Authorization with a header token and Authorization prefix in it - connection.setRequestProperty(documentJwtHeader.equals("") - ? "Authorization" : documentJwtHeader, "Bearer " + headerToken); - - String token = jwtManager.createToken(params); // encode a payload object into a body token - params.put("token", token); - } - - String bodyString = objectMapper.writeValueAsString(params); - - byte[] bodyByte = bodyString.getBytes(StandardCharsets.UTF_8); - - connection.setRequestMethod("POST"); // set the request method - connection - .setRequestProperty("Content-Type", "application/json; charset=UTF-8"); // set the Content-Type header - connection.setDoOutput(true); // set the doOutput field to true - connection.connect(); - - try (OutputStream os = connection.getOutputStream()) { - os.write(bodyByte); // write bytes to the output stream - } + public void processForceSave(final Callback callback, final String fileNameParam) { // file force saving process - InputStream stream = connection.getInputStream(); // get input stream - - if (stream == null) { - throw new RuntimeException("Could not get an answer"); - } - - String jsonString = serviceConverter.convertStreamToString(stream); // convert stream to json string - connection.disconnect(); - - JSONObject response = serviceConverter.convertStringToJSON(jsonString); // convert json string to json object - // todo: Add errors ENUM - String responseCode = response.get("error").toString(); - switch (responseCode) { - case "0": - case "4": - break; - default: - throw new RuntimeException(response.toJSONString()); - } - } - - @SneakyThrows - public void processForceSave(final Track body, final String fileNameParam) { // file force saving process - - String downloadUri = body.getUrl(); + String downloadUri = callback.getUrl(); String fileName = fileNameParam; - String curExt = fileUtility.getFileExtension(fileName); // get current file extension - String downloadExt = body.getFiletype(); // get an extension of the downloaded file + String curExt = documentManager.getExtension(fileName); // get current file extension + String downloadExt = callback.getFiletype(); // get an extension of the downloaded file + + downloadUri = urlManager.replaceToInnerDocumentServerUrl(downloadUri); Boolean newFileName = false; @@ -277,11 +240,19 @@ public void processForceSave(final Track body, final String fileNameParam) { // // todo: Extract function if (!curExt.equals(downloadExt)) { try { - // convert file and get URL to a new file - String newFileUri = serviceConverter - .getConvertedData(downloadUri, downloadExt, curExt, serviceConverter - .generateRevisionId(downloadUri), null, false, null).getUri(); - if (newFileUri.isEmpty()) { + ConvertRequest convertRequest = ConvertRequest.builder() + .key(documentManager.generateRevisionId(downloadUri)) + .url(downloadUri) + .outputtype(curExt) + .async(false) + .build(); + + // convert a file and get URL to a new file + ConvertResponse convertResponse = convertService.processConvert(convertRequest, fileName); + + String newFileUri = convertResponse.getFileUrl(); + + if (newFileUri == null || newFileUri.isEmpty()) { newFileName = true; } else { downloadUri = newFileUri; @@ -296,32 +267,34 @@ public void processForceSave(final Track body, final String fileNameParam) { // // todo: Use ENUMS // todo: Pointless toString conversion - boolean isSubmitForm = body.getForcesavetype().toString().equals("3"); + boolean isSubmitForm = callback.getForcesavetype().equals(ForcesaveType.SUBMIT_FORM); // todo: Extract function if (isSubmitForm) { // if the form is submitted if (newFileName) { // get the correct file name if it already exists fileName = documentManager - .getCorrectName(fileUtility - .getFileNameWithoutExtension(fileName) + "-form." + downloadExt); + .getCorrectName(documentManager + .getBaseName(fileName) + "-form." + downloadExt); } else { fileName = documentManager - .getCorrectName(fileUtility.getFileNameWithoutExtension(fileName) + "-form." + curExt); + .getCorrectName(documentManager.getBaseName(fileName) + "-form." + curExt); } forcesavePath = storagePathBuilder.getFileLocation(fileName); // create forcesave path if it doesn't exist - List actions = body.getActions(); - Action action = actions.get(0); + List actions = callback.getActions(); + com.onlyoffice.model.documenteditor.callback.Action action = actions.get(0); String user = action.getUserid(); // get the user ID // create meta data for the forcesaved file storageMutator.createMeta(fileName, user, "Filling Form"); try { - String formsDataUrl = body.getFormsdataurl(); + String formsDataUrl = callback.getFormsdataurl(); + + formsDataUrl = urlManager.replaceToInnerDocumentServerUrl(formsDataUrl); if (formsDataUrl != null && !formsDataUrl.isEmpty()) { - String formsName = documentManager.getCorrectName(fileUtility - .getFileNameWithoutExtension(fileName) + ".txt"); + String formsName = documentManager.getCorrectName(documentManager + .getBaseName(fileName) + ".txt"); String formsPath = storagePathBuilder.getFileLocation(formsName); byte[] byteArrayFormsData = getDownloadFile(formsDataUrl); @@ -336,7 +309,7 @@ public void processForceSave(final Track body, final String fileNameParam) { // } else { if (newFileName) { fileName = documentManager - .getCorrectName(fileUtility.getFileNameWithoutExtension(fileName) + downloadExt); + .getCorrectName(documentManager.getBaseName(fileName) + "." + downloadExt); } forcesavePath = storagePathBuilder.getForcesavePath(fileName, false); diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/document/DefaultDocumentManager.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/document/DefaultDocumentManager.java deleted file mode 100755 index 13980d724..000000000 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/document/DefaultDocumentManager.java +++ /dev/null @@ -1,246 +0,0 @@ -/** - * - * (c) Copyright Ascensio System SIA 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.onlyoffice.integration.documentserver.managers.document; - -import com.onlyoffice.integration.documentserver.storage.FileStorageMutator; -import com.onlyoffice.integration.documentserver.storage.FileStoragePathBuilder; -import com.onlyoffice.integration.documentserver.util.file.FileUtility; -import com.onlyoffice.integration.documentserver.util.service.ServiceConverter; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Primary; -import org.springframework.stereotype.Component; - -import java.io.File; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.net.InetAddress; -import java.net.URLEncoder; -import java.net.UnknownHostException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.Map; - -import static com.onlyoffice.integration.documentserver.util.Constants.KILOBYTE_SIZE; - -@Component -@Primary -public class DefaultDocumentManager implements DocumentManager { - - @Value("${files.storage.folder}") - private String storageFolder; - @Value("${files.storage}") - private String filesStorage; - @Value("${url.track}") - private String trackUrl; - @Value("${url.download}") - private String downloadUrl; - - @Autowired - private FileStorageMutator storageMutator; - @Autowired - private FileStoragePathBuilder storagePathBuilder; - @Autowired - private FileUtility fileUtility; - @Autowired - private ServiceConverter serviceConverter; - - // get URL to the created file - public String getCreateUrl(final String fileName, final Boolean sample) { - String fileExt = fileUtility.getFileExtension(fileName); - String url = storagePathBuilder.getServerUrl(true) - + "/create?fileExt=" + fileExt + "&sample=" + sample; - return url; - } - - // get a file name with an index if the file with such a name already exists - public String getCorrectName(final String fileName) { - String baseName = fileUtility.getFileNameWithoutExtension(fileName); // get file name without extension - String ext = fileUtility.getFileExtension(fileName); // get file extension - String name = baseName + "." + ext; // create a full file name - - Path path = Paths.get(storagePathBuilder.getFileLocation(name)); - - // run through all the files with such a name in the storage directory - for (int i = 1; Files.exists(path); i++) { - name = baseName + " (" + i + ")." + ext; // and add an index to the base name - path = Paths.get(storagePathBuilder.getFileLocation(name)); - } - - return name; - } - - // get file URL - public String getFileUri(final String fileName, final Boolean forDocumentServer) { - try { - String serverPath = storagePathBuilder.getServerUrl(forDocumentServer); // get server URL - String hostAddress = storagePathBuilder.getStorageLocation(); // get the storage directory - String filePathDownload = !fileName.contains(InetAddress.getLocalHost().getHostAddress()) ? fileName - : fileName.substring(fileName.indexOf(InetAddress.getLocalHost() - .getHostAddress()) + InetAddress.getLocalHost().getHostAddress().length() + 1); - if (!filesStorage.isEmpty() && filePathDownload.contains(filesStorage)) { - filePathDownload = filePathDownload.substring(filesStorage.length() + 1); - } - - String filePath = serverPath + "/download?fileName=" + URLEncoder - .encode(filePathDownload, java.nio.charset.StandardCharsets.UTF_8.toString()) + "&userAddress" - + URLEncoder.encode(hostAddress, java.nio.charset.StandardCharsets.UTF_8.toString()); - return filePath; - } catch (UnsupportedEncodingException | UnknownHostException e) { - return ""; - } - } - - // get file URL - public String getHistoryFileUrl(final String fileName, final Integer version, final String file, - final Boolean forDocumentServer) { - try { - String serverPath = storagePathBuilder.getServerUrl(forDocumentServer); // get server URL - String hostAddress = storagePathBuilder.getStorageLocation(); // get the storage directory - String filePathDownload = !fileName.contains(InetAddress.getLocalHost().getHostAddress()) ? fileName - : fileName.substring(fileName.indexOf(InetAddress.getLocalHost().getHostAddress()) - + InetAddress.getLocalHost().getHostAddress().length() + 1); - String userAddress = forDocumentServer ? "&userAddress" + URLEncoder - .encode(hostAddress, java.nio.charset.StandardCharsets.UTF_8.toString()) : ""; - String filePath = serverPath + "/downloadhistory?fileName=" + URLEncoder - .encode(filePathDownload, java.nio.charset.StandardCharsets.UTF_8.toString()) - + "&ver=" + version + "&file=" + file - + userAddress; - return filePath; - } catch (UnsupportedEncodingException | UnknownHostException e) { - return ""; - } - } - - // get the callback URL - public String getCallback(final String fileName) { - String serverPath = storagePathBuilder.getServerUrl(true); - String storageAddress = storagePathBuilder.getStorageLocation(); - try { - String query = trackUrl + "?fileName=" - + URLEncoder.encode(fileName, java.nio.charset.StandardCharsets.UTF_8.toString()) - + "&userAddress=" + URLEncoder - .encode(storageAddress, java.nio.charset.StandardCharsets.UTF_8.toString()); - return serverPath + query; - } catch (UnsupportedEncodingException e) { - return ""; - } - } - - // get URL to download a file - public String getDownloadUrl(final String fileName, final Boolean isServer) { - String serverPath = storagePathBuilder.getServerUrl(isServer); - String storageAddress = storagePathBuilder.getStorageLocation(); - try { - String userAddress = isServer ? "&userAddress=" + URLEncoder - .encode(storageAddress, java.nio.charset.StandardCharsets.UTF_8.toString()) : ""; - String query = downloadUrl + "?fileName=" - + URLEncoder.encode(fileName, java.nio.charset.StandardCharsets.UTF_8.toString()) - + userAddress; - - return serverPath + query; - } catch (UnsupportedEncodingException e) { - return ""; - } - } - - // get file information - public ArrayList> getFilesInfo() { - ArrayList> files = new ArrayList<>(); - - // run through all the stored files - for (File file : storageMutator.getStoredFiles()) { - Map map = new LinkedHashMap<>(); // write all the parameters to the map - map.put("version", storagePathBuilder.getFileVersion(file.getName(), false)); - map.put("id", serviceConverter - .generateRevisionId(storagePathBuilder.getStorageLocation() - + "/" + file.getName() + "/" - + Paths.get(storagePathBuilder.getFileLocation(file.getName())) - .toFile() - .lastModified())); - map.put("contentLength", new BigDecimal(String.valueOf((file.length() / Double.valueOf(KILOBYTE_SIZE)))) - .setScale(2, RoundingMode.HALF_UP) + " KB"); - map.put("pureContentLength", file.length()); - map.put("title", file.getName()); - map.put("updated", String.valueOf(new Date(file.lastModified()))); - files.add(map); - } - - return files; - } - - // get file information by its ID - public ArrayList> getFilesInfo(final String fileId) { - ArrayList> file = new ArrayList<>(); - - for (Map map : getFilesInfo()) { - if (map.get("id").equals(fileId)) { - file.add(map); - break; - } - } - - return file; - } - - // get the path to the file version by the history path and file version - public String versionDir(final String path, final Integer version, final boolean historyPath) { - if (!historyPath) { - return storagePathBuilder.getHistoryDir(storagePathBuilder.getFileLocation(path)) + version; - } - return path + File.separator + version; - } - - // create demo document - public String createDemo(final String fileExt, final Boolean sample, final String uid, final String uname) { - String demoName = (sample ? "sample." : "new.") - + fileExt; // create sample or new template file with the necessary extension - String demoPath = - "assets" - + File.separator - + "document-templates" - + File.separator - + (sample ? "sample" : "new") - + File.separator - + demoName; - - // get a file name with an index if the file with such a name already exists - String fileName = getCorrectName(demoName); - - InputStream stream = Thread.currentThread() - .getContextClassLoader() - .getResourceAsStream(demoPath); // get the input file stream - - if (stream == null) { - return null; - } - - storageMutator.createFile(Path.of(storagePathBuilder - .getFileLocation(fileName)), stream); // create a file in the specified directory - storageMutator.createMeta(fileName, uid, uname); // create meta information of the demo file - - return fileName; - } -} diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/document/DocumentManager.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/document/DocumentManager.java deleted file mode 100755 index cb84ae4ab..000000000 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/document/DocumentManager.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * - * (c) Copyright Ascensio System SIA 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.onlyoffice.integration.documentserver.managers.document; - -import java.util.ArrayList; -import java.util.Map; - -// specify the document manager functions -public interface DocumentManager { - - // get a file name with an index if the file with such a name already exists - String getCorrectName(String fileName); - String getFileUri(String fileName, Boolean forDocumentServer); // get file URL - String getHistoryFileUrl(String fileName, Integer version, String file, Boolean forDocumentServer); // get file URL - String getCallback(String fileName); // get the callback URL - String getDownloadUrl(String fileName, Boolean forDocumentServer); // get URL to download a file - ArrayList> getFilesInfo(); // get file information - ArrayList> getFilesInfo(String fileId); // get file information by its ID - - // get the path to the file version by the history path and file version - String versionDir(String path, Integer version, boolean historyPath); - - // create demo document - String createDemo(String fileExt, Boolean sample, String uid, String uname) throws Exception; - String getCreateUrl(String fileName, Boolean sample); // get URL to the created file -} diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/history/DefaultHistoryManager.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/history/DefaultHistoryManager.java index 60db4d204..0d03f393c 100644 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/history/DefaultHistoryManager.java +++ b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/history/DefaultHistoryManager.java @@ -20,14 +20,17 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.onlyoffice.integration.documentserver.managers.document.DocumentManager; -import com.onlyoffice.integration.documentserver.managers.jwt.JwtManager; -import com.onlyoffice.integration.documentserver.models.filemodel.Document; import com.onlyoffice.integration.documentserver.storage.FileStoragePathBuilder; -import com.onlyoffice.integration.documentserver.util.file.FileUtility; -import com.onlyoffice.integration.documentserver.util.service.ServiceConverter; +import com.onlyoffice.integration.sdk.manager.DocumentManager; +import com.onlyoffice.integration.sdk.manager.UrlManager; +import com.onlyoffice.manager.security.JwtManager; +import com.onlyoffice.manager.settings.SettingsManager; +import com.onlyoffice.model.common.User; +import com.onlyoffice.model.documenteditor.HistoryData; +import com.onlyoffice.model.documenteditor.callback.History; +import com.onlyoffice.model.documenteditor.history.Version; +import com.onlyoffice.model.documenteditor.historydata.Previous; import lombok.SneakyThrows; -import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.springframework.beans.factory.annotation.Autowired; @@ -35,6 +38,7 @@ import java.io.File; import java.io.FileInputStream; +import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -49,15 +53,9 @@ public class DefaultHistoryManager implements HistoryManager { @Autowired private FileStoragePathBuilder storagePathBuilder; - @Autowired - private DocumentManager documentManager; - @Autowired private JwtManager jwtManager; - @Autowired - private FileUtility fileUtility; - @Autowired private JSONParser parser; @@ -65,109 +63,13 @@ public class DefaultHistoryManager implements HistoryManager { private ObjectMapper objectMapper; @Autowired - private ServiceConverter serviceConverter; - - // todo: Refactoring - @SneakyThrows - public String[] getHistory(final Document document) { // get document history - - // get history directory - String histDir = storagePathBuilder.getHistoryDir(storagePathBuilder.getFileLocation(document.getTitle())); - Integer curVer = storagePathBuilder.getFileVersion(histDir, false); // get current file version - - if (curVer > 0) { // check if the current file version is greater than 0 - List hist = new ArrayList<>(); - Map histData = new HashMap<>(); - - for (Integer i = 1; i <= curVer; i++) { // run through all the file versions - Map obj = new HashMap(); - Map dataObj = new HashMap(); - String verDir = documentManager - .versionDir(histDir, i, true); // get the path to the given file version - - String key = i == curVer ? document.getKey() : readFileToEnd(new File(verDir - + File.separator + "key.txt")); // get document key - obj.put("key", key); - obj.put("version", i); - - if (i == 1) { // check if the version number is equal to 1 - String createdInfo = readFileToEnd(new File(histDir - + File.separator + "createdInfo.json")); // get file with meta data - JSONObject json = (JSONObject) parser.parse(createdInfo); // and turn it into json object - - // write meta information to the object (user information and creation date) - obj.put("created", json.get("created")); - Map user = new HashMap(); - user.put("id", json.get("id")); - user.put("name", json.get("name")); - obj.put("user", user); - } - - dataObj.put("fileType", fileUtility - .getFileExtension(document.getTitle())); - dataObj.put("key", key); - dataObj.put("url", i == curVer ? document.getUrl() - : documentManager.getHistoryFileUrl(document.getTitle(), i, "prev." + fileUtility - .getFileExtension(document.getTitle()), true)); - if (!document.getDirectUrl().equals("")) { - dataObj.put("directUrl", i == curVer ? document.getDirectUrl() - : documentManager.getHistoryFileUrl(document.getTitle(), i, "prev." + fileUtility - .getFileExtension(document.getTitle()), false)); - } - dataObj.put("version", i); - - if (i > 1) { //check if the version number is greater than 1 - // if so, get the path to the changes.json file - JSONObject changes = (JSONObject) parser.parse(readFileToEnd(new File(documentManager - .versionDir(histDir, i - 1, true) + File.separator + "changes.json"))); - JSONObject change = (JSONObject) ((JSONArray) changes.get("changes")).get(0); - - // write information about changes to the object - obj.put("changes", changes.get("changes")); - obj.put("serverVersion", changes.get("serverVersion")); - obj.put("created", change.get("created")); - obj.put("user", change.get("user")); - - // get the history data from the previous file version - Map prev = (Map) histData.get(Integer.toString(i - 2)); - Map prevInfo = new HashMap(); - prevInfo.put("fileType", prev.get("fileType")); - prevInfo.put("key", prev.get("key")); // write key and URL information about previous file version - prevInfo.put("url", prev.get("url")); - if (!document.getDirectUrl().equals("")) { - prevInfo.put("directUrl", prev.get("directUrl")); - } - - // write information about previous file version to the data object - dataObj.put("previous", prevInfo); - // write the path to the diff.zip archive with differences in this file version - Integer verdiff = i - 1; - dataObj.put("changesUrl", documentManager - .getHistoryFileUrl(document.getTitle(), verdiff, "diff.zip", true)); - } + private SettingsManager settingsManager; - if (jwtManager.tokenEnabled()) { - dataObj.put("token", jwtManager.createToken(dataObj)); - } - - hist.add(obj); - histData.put(Integer.toString(i - 1), dataObj); - } - - // write history information about the current file version to the history object - Map histObj = new HashMap(); - histObj.put("currentVersion", curVer); - histObj.put("history", hist); + @Autowired + private UrlManager urlManager; - try { - return new String[]{objectMapper.writeValueAsString(histObj), - objectMapper.writeValueAsString(histData)}; - } catch (JsonProcessingException e) { - e.printStackTrace(); - } - } - return new String[]{"", ""}; - } + @Autowired + private DocumentManager documentManager; // todo: Refactoring @SneakyThrows @@ -178,16 +80,14 @@ public String getHistory(final String fileName) { // get document history Integer curVer = storagePathBuilder.getFileVersion(histDir, false); // get current file version if (curVer > 0) { // check if the current file version is greater than 0 - List hist = new ArrayList<>(); + List history = new ArrayList<>(); for (Integer i = 1; i <= curVer; i++) { // run through all the file versions - Map obj = new HashMap(); - String verDir = documentManager - .versionDir(histDir, i, true); // get the path to the given file version + String verDir = versionDir(histDir, i, true); // get the path to the given file version String key; if (i == curVer) { - key = serviceConverter + key = documentManager .generateRevisionId(storagePathBuilder.getStorageLocation() + "/" + fileName + "/" + new File(storagePathBuilder.getFileLocation(fileName)).lastModified()); @@ -195,8 +95,10 @@ public String getHistory(final String fileName) { // get document history key = readFileToEnd(new File(verDir + File.separator + "key.txt")); } - obj.put("key", key); - obj.put("version", i); + Version version = Version.builder() + .key(key) + .version(String.valueOf(i)) + .build(); if (i == 1) { // check if the version number is equal to 1 String createdInfo = readFileToEnd(new File(histDir @@ -204,33 +106,35 @@ public String getHistory(final String fileName) { // get document history JSONObject json = (JSONObject) parser.parse(createdInfo); // and turn it into json object // write meta information to the object (user information and creation date) - obj.put("created", json.get("created")); - Map user = new HashMap(); - user.put("id", json.get("id")); - user.put("name", json.get("name")); - obj.put("user", user); + version.setCreated(String.valueOf(json.get("created"))); + version.setUser(User.builder() + .id(String.valueOf(json.get("id"))) + .name(String.valueOf(json.get("name"))) + .build() + ); } if (i > 1) { //check if the version number is greater than 1 // if so, get the path to the changes.json file - JSONObject changes = (JSONObject) parser.parse(readFileToEnd(new File(documentManager - .versionDir(histDir, i - 1, true) + File.separator + "changes.json"))); - JSONObject change = (JSONObject) ((JSONArray) changes.get("changes")).get(0); + InputStream changesSteam = new FileInputStream( + versionDir(histDir, i - 1, true) + File.separator + "changes.json"); + + History changes = objectMapper.readValue(changesSteam, History.class); // write information about changes to the object - obj.put("changes", changes.get("changes")); - obj.put("serverVersion", changes.get("serverVersion")); - obj.put("created", change.get("created")); - obj.put("user", change.get("user")); + version.setChanges(changes.getChanges()); + version.setServerVersion(changes.getServerVersion()); + version.setCreated(changes.getChanges().get(0).getCreated()); + version.setUser(changes.getChanges().get(0).getUser()); } - hist.add(obj); + history.add(version); } // write history information about the current file version to the history object Map histObj = new HashMap(); histObj.put("currentVersion", curVer); - histObj.put("history", hist); + histObj.put("history", history); try { return objectMapper.writeValueAsString(histObj); @@ -243,74 +147,65 @@ public String getHistory(final String fileName) { // get document history // todo: Refactoring @SneakyThrows - public String getHistoryData(final String fileName, final String version, final Boolean directUrl) { + public String getHistoryData(final String fileName, final String version) { // get history directory String histDir = storagePathBuilder.getHistoryDir(storagePathBuilder.getFileLocation(fileName)); Integer curVer = storagePathBuilder.getFileVersion(histDir, false); // get current file version if (curVer > 0) { // check if the current file version is greater than 0 - Map histData = new HashMap<>(); + Map historyDataMap = new HashMap<>(); for (Integer i = 1; i <= curVer; i++) { // run through all the file versions - Map dataObj = new HashMap(); - String verDir = documentManager - .versionDir(histDir, i, true); // get the path to the given file version + String verDir = versionDir(histDir, i, true); // get the path to the given file version String key; if (i == curVer) { - key = serviceConverter + key = documentManager .generateRevisionId(storagePathBuilder.getStorageLocation() + "/" + fileName + "/" + new File(storagePathBuilder.getFileLocation(fileName)).lastModified()); } else { key = readFileToEnd(new File(verDir + File.separator + "key.txt")); } + HistoryData historyData = HistoryData.builder() + .fileType(documentManager.getExtension(fileName)) + .key(key) + .url(i == curVer ? urlManager.getFileUrl(fileName) + : urlManager.getHistoryFileUrl(fileName, i, "prev" + documentManager + .getExtension(fileName), true)) + .build(); - dataObj.put("fileType", fileUtility - .getFileExtension(fileName).replace(".", "")); - dataObj.put("key", key); - dataObj.put("url", i == curVer ? documentManager.getDownloadUrl(fileName, true) - : documentManager.getHistoryFileUrl(fileName, i, "prev" + fileUtility - .getFileExtension(fileName), true)); - if (directUrl) { - dataObj.put("directUrl", i == curVer - ? documentManager.getDownloadUrl(fileName, false) - : documentManager.getHistoryFileUrl(fileName, i, "prev" + fileUtility - .getFileExtension(fileName), false)); - } - dataObj.put("version", i); + historyData.setVersion(String.valueOf(i)); if (i > 1) { //check if the version number is greater than 1 Integer verdiff = i - 1; // get the history data from the previous file version - Map prev = (Map) histData.get(Integer.toString(verdiff)); - Map prevInfo = new HashMap(); - prevInfo.put("fileType", prev.get("fileType")); - prevInfo.put("key", prev.get("key")); // write key and URL information about previous file version - prevInfo.put("url", prev.get("url")); - if (directUrl) { - prevInfo.put("directUrl", prev.get("directUrl")); - } + HistoryData historyDataPrev = historyDataMap.get(Integer.toString(verdiff)); + Previous previous = Previous.builder() + .fileType(historyDataPrev.getFileType()) + .key(historyDataPrev.getKey()) + .url(historyDataPrev.getUrl()) + .build(); // write information about previous file version to the data object - dataObj.put("previous", prevInfo); + historyData.setPrevious(previous); if (diffExists(histDir, verdiff)) { // write the path to the diff.zip archive with differences in this file version - dataObj.put("changesUrl", documentManager + historyData.setChangesUrl(urlManager .getHistoryFileUrl(fileName, verdiff, "diff.zip", true)); } } - if (jwtManager.tokenEnabled()) { - dataObj.put("token", jwtManager.createToken(dataObj)); + if (settingsManager.isSecurityEnabled()) { + historyData.setToken(jwtManager.createToken(historyData)); } - histData.put(Integer.toString(i), dataObj); + historyDataMap.put(Integer.toString(i), historyData); } try { - return objectMapper.writeValueAsString(histData.get(version)); + return objectMapper.writeValueAsString(historyDataMap.get(version)); } catch (JsonProcessingException e) { e.printStackTrace(); } @@ -318,6 +213,14 @@ public String getHistoryData(final String fileName, final String version, final return ""; } + @Override + public String versionDir(final String path, final Integer version, final boolean historyPath) { + if (!historyPath) { + return storagePathBuilder.getHistoryDir(storagePathBuilder.getFileLocation(path)) + version; + } + return path + File.separator + version; + } + // read a file private String readFileToEnd(final File file) { diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/history/HistoryManager.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/history/HistoryManager.java index af72f58de..8330190af 100644 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/history/HistoryManager.java +++ b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/history/HistoryManager.java @@ -21,6 +21,6 @@ // specify the history manager functions public interface HistoryManager { String getHistory(String fileName); // get document history - - String getHistoryData(String fileName, String version, Boolean directUrl); // get document history data + String getHistoryData(String fileName, String version); // get document history data + String versionDir(String path, Integer version, boolean historyPath); } diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/jwt/DefaultJwtManager.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/jwt/DefaultJwtManager.java deleted file mode 100644 index b0f90852a..000000000 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/jwt/DefaultJwtManager.java +++ /dev/null @@ -1,130 +0,0 @@ -/** - * - * (c) Copyright Ascensio System SIA 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.onlyoffice.integration.documentserver.managers.jwt; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.primeframework.jwt.Signer; -import org.primeframework.jwt.Verifier; -import org.primeframework.jwt.domain.JWT; -import org.primeframework.jwt.hmac.HMACSigner; -import org.primeframework.jwt.hmac.HMACVerifier; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import java.util.LinkedHashMap; -import java.util.Map; - -@Component -public class DefaultJwtManager implements JwtManager { - @Value("${files.docservice.secret}") - private String tokenSecret; - @Value("${files.docservice.token-use-for-request}") - private String tokenUseForRequest; - @Autowired - private ObjectMapper objectMapper; - @Autowired - private JSONParser parser; - - // create document token - public String createToken(final Map payloadClaims) { - try { - // build a HMAC signer using a SHA-256 hash - Signer signer = HMACSigner.newSHA256Signer(tokenSecret); - JWT jwt = new JWT(); - for (String key : payloadClaims.keySet()) { // run through all the keys from the payload - jwt.addClaim(key, payloadClaims.get(key)); // and write each claim to the jwt - } - return JWT.getEncoder().encode(jwt, signer); // sign and encode the JWT to a JSON string representation - } catch (Exception e) { - return ""; - } - } - - // check if the token is enabled - public boolean tokenEnabled() { - return tokenSecret != null && !tokenSecret.isEmpty(); - } - - public boolean tokenUseForRequest() { - return Boolean.parseBoolean(tokenUseForRequest) && !tokenUseForRequest.isEmpty(); - } - - // read document token - public JWT readToken(final String token) { - try { - // build a HMAC verifier using the token secret - Verifier verifier = HMACVerifier.newVerifier(tokenSecret); - - // verify and decode the encoded string JWT to a rich object - return JWT.getDecoder().decode(token, verifier); - } catch (Exception exception) { - return null; - } - } - - // parse the body - public JSONObject parseBody(final String payload, final String header) { - JSONObject body; - try { - Object obj = parser.parse(payload); // get body parameters by parsing the payload - body = (JSONObject) obj; - } catch (Exception ex) { - throw new RuntimeException("{\"error\":1,\"message\":\"JSON Parsing error\"}"); - } - if (tokenEnabled() && tokenUseForRequest()) { // check if the token is enabled - String token = (String) body.get("token"); // get token from the body - if (token == null) { // if token is empty - if (header != null && !header.isBlank()) { // and the header is defined - - // get token from the header (it is placed after the Bearer prefix if it exists) - token = header.startsWith("Bearer ") ? header.substring("Bearer ".length()) : header; - } - } - if (token == null || token.isBlank()) { - throw new RuntimeException("{\"error\":1,\"message\":\"JWT expected\"}"); - } - - JWT jwt = readToken(token); // read token - if (jwt == null) { - throw new RuntimeException("{\"error\":1,\"message\":\"JWT validation failed\"}"); - } - if (jwt.getObject("payload") != null) { // get payload from the token and check if it is not empty - try { - @SuppressWarnings("unchecked") LinkedHashMap jwtPayload = - (LinkedHashMap) jwt.getObject("payload"); - - jwt.claims = jwtPayload; - } catch (Exception ex) { - throw new RuntimeException("{\"error\":1,\"message\":\"Wrong payload\"}"); - } - } - try { - Object obj = parser.parse(objectMapper.writeValueAsString(jwt.claims)); - body = (JSONObject) obj; - } catch (Exception ex) { - throw new RuntimeException("{\"error\":1,\"message\":\"Parsing error\"}"); - } - } - - return body; - } -} diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/jwt/JwtManager.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/jwt/JwtManager.java deleted file mode 100644 index 65190cc1c..000000000 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/jwt/JwtManager.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * - * (c) Copyright Ascensio System SIA 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.onlyoffice.integration.documentserver.managers.jwt; - -import org.json.simple.JSONObject; -import org.primeframework.jwt.domain.JWT; - -import java.util.Map; - -// specify the jwt manager functions -public interface JwtManager { - boolean tokenEnabled(); // check if the token is enabled - boolean tokenUseForRequest(); // check if the token is enabled - String createToken(Map payloadClaims); // create document token - JWT readToken(String token); // read document token - JSONObject parseBody(String payload, String header); // parse the body -} diff --git a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/template/SampleTemplateManager.java b/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/template/SampleTemplateManager.java deleted file mode 100644 index ef528fbbf..000000000 --- a/web/documentserver-example/java-spring/src/main/java/com/onlyoffice/integration/documentserver/managers/template/SampleTemplateManager.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * - * (c) Copyright Ascensio System SIA 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.onlyoffice.integration.documentserver.managers.template; - -import com.onlyoffice.integration.documentserver.models.enums.DocumentType; -import com.onlyoffice.integration.documentserver.models.filemodel.Template; -import com.onlyoffice.integration.documentserver.managers.document.DocumentManager; -import com.onlyoffice.integration.documentserver.storage.FileStoragePathBuilder; -import com.onlyoffice.integration.documentserver.util.file.FileUtility; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -import java.util.List; - -@Component -@Qualifier("sample") -public class SampleTemplateManager implements TemplateManager { - @Autowired - private DocumentManager documentManager; - - @Autowired - private FileStoragePathBuilder storagePathBuilder; - - @Autowired - private FileUtility fileUtility; - - // create a template document with the specified name - public List