diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/OnlyOfficeEditor.java b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/OnlyOfficeEditor.java index 9eadb45df04604075621b5512d94c2ae6125df3b..664410d3dbc467060a3f99a468b3ddae3da4f112 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/OnlyOfficeEditor.java +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/OnlyOfficeEditor.java @@ -46,6 +46,8 @@ public class OnlyOfficeEditor implements DocEditor { @Autowired private OnlyOfficeModule onlyOfficeModule; + @Autowired + private OnlyOfficeService onlyOfficeService; @Override public boolean isEnable() { @@ -65,8 +67,7 @@ public class OnlyOfficeEditor implements DocEditor { @Override public boolean isSupportingFormat(String suffix, Mode mode, boolean hasMeta) { - // TODO uh Auto-generated method stub - return "docx".equals(suffix); + return hasMeta && onlyOfficeService.isSupportedFormat(suffix, mode); } @Override diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/OnlyOfficeService.java b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/OnlyOfficeService.java index bf91b6631fa36bea82da9e36149bd08b083d8aaa..beb9ee803d9ec6d5e1dde98742b8df144395b92c 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/OnlyOfficeService.java +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/OnlyOfficeService.java @@ -21,8 +21,7 @@ package org.olat.core.commons.services.doceditor.onlyoffice; import java.io.File; -import org.olat.core.commons.services.doceditor.DocEditorSecurityCallback; -import org.olat.core.commons.services.doceditor.wopi.Access; +import org.olat.core.commons.services.doceditor.DocEditor.Mode; import org.olat.core.commons.services.vfs.VFSMetadata; import org.olat.core.id.Identity; @@ -37,12 +36,16 @@ public interface OnlyOfficeService { boolean fileExists(String fileId); File getFile(String fileId); - - VFSMetadata getMetadata(String fileId); - - Access createAccess(VFSMetadata vfsMetadata, Identity identity, DocEditorSecurityCallback secCallback); - Access getAccess(String accessToken); + boolean canUpdateContent(String fileId, Identity identity); + + boolean updateContent(String fileId, Identity identity, String url); - void deleteAccess(Access access); + boolean isSupportedFormat(String suffix, Mode mode); + + String getEditorDocumentType(String suffix); + + String getDocumentKey(VFSMetadata metadata); + + Identity getIdentity(String identityId); } diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/manager/Formats.java b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/manager/Formats.java new file mode 100644 index 0000000000000000000000000000000000000000..4cf41526c90967a40cf4b2e2c037fac474c95f07 --- /dev/null +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/manager/Formats.java @@ -0,0 +1,115 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.core.commons.services.doceditor.onlyoffice.manager; + +import java.util.Arrays; +import java.util.List; + +import org.olat.core.commons.services.doceditor.DocEditor.Mode; + +/** + * + * Initial date: 15 Apr 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class Formats { + + // see https://api.onlyoffice.com/editors/config/ + // see https://helpcenter.onlyoffice.com/ONLYOFFICE-Editors/ONLYOFFICE-Document-Editor/HelpfulHints/SupportedFormats.aspx + // see https://helpcenter.onlyoffice.com/ONLYOFFICE-Editors/ONLYOFFICE-Spreadsheet-Editor/HelpfulHints/SupportedFormats.aspx + // see https://helpcenter.onlyoffice.com/ONLYOFFICE-Editors/ONLYOFFICE-Presentation-Editor/HelpfulHints/SupportedFormats.aspx + + private static List<String> TEXT_EDIT = Arrays.asList( + "docm", + "docx", + "dotx", + "odt", + "ott", + "rtf", + "txt" + ); + private static List<String> TEXT_VIEW = Arrays.asList( + "doc", + "dot", + "dotm", + "epub", + "fodt", + "mht", + "pdf", + "djvu", + "xps" + ); + private static List<String> SPREADSHEET_EDIT = Arrays.asList( + "csv", + "ods", + "ots", + "xlsx", + "xltx" + ); + private static List<String> SPREADSHEET_VIEW = Arrays.asList( + "fods", + "xls", + "xlsm", + "xlt", + "xltm" + ); + private static List<String> PRESENTATION_EDIT = Arrays.asList( + "fodp", + "odp", + "otp", + "pot", + "potm", + "potx", + "ppsx", + "pptx" + ); + private static List<String> PRESENTATION_VIEW = Arrays.asList( + "pps", + "ppsm", + "ppt", + "pptm" + ); + + public static boolean isSupportedFormat(String suffix, Mode mode) { + String lowerSuffix = suffix.toLowerCase(); + if (TEXT_EDIT.contains(lowerSuffix)) return true; + if (SPREADSHEET_EDIT.contains(lowerSuffix)) return true; + if (PRESENTATION_EDIT.contains(lowerSuffix)) return true; + if (Mode.VIEW.equals(mode)) { + if (TEXT_VIEW.contains(lowerSuffix)) return true; + if (SPREADSHEET_VIEW.contains(lowerSuffix)) return true; + if (PRESENTATION_VIEW.contains(lowerSuffix)) return true; + } + return false; + } + + public static String getEditorType(String suffix) { + String lowerSuffix = suffix.toLowerCase(); + if (TEXT_EDIT.contains(lowerSuffix)) return "text"; + if (TEXT_VIEW.contains(lowerSuffix)) return "text"; + if (SPREADSHEET_EDIT.contains(lowerSuffix)) return "spreadsheet"; + if (SPREADSHEET_VIEW.contains(lowerSuffix)) return "spreadsheet"; + if (PRESENTATION_EDIT.contains(lowerSuffix)) return "presentation"; + if (PRESENTATION_VIEW.contains(lowerSuffix)) return "presentation"; + return null; + } + +} diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/manager/OnlyOfficeServiceImpl.java b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/manager/OnlyOfficeServiceImpl.java index 657a65e14996919b1d82e7e2f13d01d5625bd9d2..bc3e6d0a1dd796930c2fe47fde2244d727bced69 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/manager/OnlyOfficeServiceImpl.java +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/manager/OnlyOfficeServiceImpl.java @@ -20,13 +20,23 @@ package org.olat.core.commons.services.doceditor.onlyoffice.manager; import java.io.File; +import java.io.InputStream; +import java.net.URL; +import java.text.DateFormat; +import java.text.SimpleDateFormat; -import org.olat.core.commons.services.doceditor.DocEditorSecurityCallback; +import org.olat.basesecurity.BaseSecurityManager; +import org.olat.core.commons.services.doceditor.DocEditor.Mode; import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeService; -import org.olat.core.commons.services.doceditor.wopi.Access; import org.olat.core.commons.services.doceditor.wopi.WopiService; import org.olat.core.commons.services.vfs.VFSMetadata; +import org.olat.core.commons.services.vfs.VFSRepositoryService; import org.olat.core.id.Identity; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.vfs.VFSConstants; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.core.util.vfs.VFSManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -39,8 +49,16 @@ import org.springframework.stereotype.Service; @Service public class OnlyOfficeServiceImpl implements OnlyOfficeService { + private static final OLog log = Tracing.createLoggerFor(OnlyOfficeServiceImpl.class); + + private static DateFormat LAST_MODIFIED = new SimpleDateFormat("yyyyMMddHHmmSS"); + @Autowired private WopiService wopiService; + @Autowired + private VFSRepositoryService vfsRepositoryService; + @Autowired + private BaseSecurityManager securityManager; @Override public boolean fileExists(String fileId) { @@ -53,25 +71,61 @@ public class OnlyOfficeServiceImpl implements OnlyOfficeService { } @Override - public VFSMetadata getMetadata(String fileId) { - return wopiService.getMetadata(fileId); + public boolean canUpdateContent(String fileId, Identity identity) { + return true; + //TODO uh check lock +// VFSLeaf vfsLeaf = wopiService.getVfsLeaf(fileId); +// return !isLockedForMe(vfsLeaf, access.getIdentity()); + } + + @Override + public boolean updateContent(String fileId, Identity identity, String url) { + VFSLeaf vfsLeaf = wopiService.getVfsLeaf(fileId); + boolean updated = false; + try (InputStream in = new URL(url).openStream()) { + //TODO uh versionCntrolled + if(//access.isVersionControlled() && + vfsLeaf.canVersion() == VFSConstants.YES) { + updated = vfsRepositoryService.addVersion(vfsLeaf, identity, "OnlyOffice", in); + } else { + updated = VFSManager.copyContent(in, vfsLeaf); + } + } catch(Exception e) { + log.error("", e); + } + if (updated) { + log.debug("File updated. File ID: " + fileId); + //TODO uh lock +// refreshLock(vfsLeaf); + } + return updated; + } + + @Override + public boolean isSupportedFormat(String suffix, Mode mode) { + return Formats.isSupportedFormat(suffix, mode); } @Override - public Access createAccess(VFSMetadata vfsMetadata, Identity identity, DocEditorSecurityCallback secCallback) { - return wopiService.createAccess(vfsMetadata, identity, secCallback); + public String getEditorDocumentType(String suffix) { + return Formats.getEditorType(suffix); } @Override - public Access getAccess(String accessToken) { - return wopiService.getAccess(accessToken); + public String getDocumentKey(VFSMetadata metadata) { + String lastModified = LAST_MODIFIED.format(metadata.getLastModified()); + return metadata.getUuid() + "-" + lastModified; } @Override - public void deleteAccess(Access access) { - if (access == null) return; - - wopiService.deleteAccess(access.getToken()); + public Identity getIdentity(String identityId) { + try { + Long identityKey = Long.valueOf(identityId); + return securityManager.loadIdentityByKey(identityKey); + } catch (NumberFormatException e) { + log.warn("Try to load identity with key " + identityId, e); + } + return null; } } diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/restapi/CallbackResponseVO.java b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/restapi/CallbackResponseVO.java new file mode 100644 index 0000000000000000000000000000000000000000..3958990ad01cd5bbc5f83c7128e1f05f52442422 --- /dev/null +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/restapi/CallbackResponseVO.java @@ -0,0 +1,51 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.core.commons.services.doceditor.onlyoffice.restapi; + +/** + * + * Initial date: 12 Apr 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class CallbackResponseVO { + + private static final CallbackResponseVO SUCCESS = new CallbackResponseVO(0); + private static final CallbackResponseVO ERROR = new CallbackResponseVO(1); + + private final int error; + + static CallbackResponseVO success() { + return SUCCESS; + } + + static CallbackResponseVO error() { + return ERROR; + } + + private CallbackResponseVO(int error) { + this.error = error; + } + + public int getError() { + return error; + } + +} diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/restapi/CallbackVO.java b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/restapi/CallbackVO.java new file mode 100644 index 0000000000000000000000000000000000000000..7cb85686fef6c65148603be9759057a99f661d07 --- /dev/null +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/restapi/CallbackVO.java @@ -0,0 +1,145 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.core.commons.services.doceditor.onlyoffice.restapi; + +import java.util.Arrays; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * + * Initial date: 12 Apr 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class CallbackVO { + + // see https://api.onlyoffice.com/editors/callback + + static final int STATUS_READY_FOR_SAVING = 2; + + private List<Action> actions; + private Integer forcesavetype; + private String key; + private Integer status; + private String url; + private String[] users; + + public List<Action> getActions() { + return actions; + } + + public void setActions(List<Action> actions) { + this.actions = actions; + } + + public Integer getForcesavetype() { + return forcesavetype; + } + + public void setForcesavetype(Integer forcesavetype) { + this.forcesavetype = forcesavetype; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String[] getUsers() { + return users; + } + + public void setUsers(String[] users) { + this.users = users; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("CallbackVO [actions="); + builder.append(actions); + builder.append(", forcesavetype="); + builder.append(forcesavetype); + builder.append(", key="); + builder.append(key); + builder.append(", status="); + builder.append(status); + builder.append(", url="); + builder.append(url); + builder.append(", users="); + builder.append(Arrays.toString(users)); + builder.append("]"); + return builder.toString(); + } + + public static class Action { + + private Integer type; + private String userid; + + public Integer getType() { + return type; + } + public void setType(Integer type) { + this.type = type; + } + public String getUserid() { + return userid; + } + public void setUserid(String userid) { + this.userid = userid; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Action [type="); + builder.append(type); + builder.append(", userid="); + builder.append(userid); + builder.append("]"); + return builder.toString(); + } + + } + +} diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/restapi/OnlyOfficeWebService.java b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/restapi/OnlyOfficeWebService.java index 5316d65363bc1951635801fce4e4e3d01a4b2436..33be973f60664200047cb8326717d99927d5dd6d 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/restapi/OnlyOfficeWebService.java +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/restapi/OnlyOfficeWebService.java @@ -19,16 +19,21 @@ */ package org.olat.core.commons.services.doceditor.onlyoffice.restapi; +import static org.olat.core.commons.services.doceditor.onlyoffice.restapi.CallbackResponseVO.error; +import static org.olat.core.commons.services.doceditor.onlyoffice.restapi.CallbackResponseVO.success; +import static org.olat.core.commons.services.doceditor.onlyoffice.restapi.CallbackVO.STATUS_READY_FOR_SAVING; + import java.io.File; import java.util.List; import java.util.Map.Entry; import java.util.stream.Collectors; +import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; -import javax.ws.rs.QueryParam; +import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; @@ -37,7 +42,7 @@ import javax.ws.rs.core.Response.Status; import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeModule; import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeService; -import org.olat.core.commons.services.doceditor.wopi.Access; +import org.olat.core.id.Identity; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.springframework.beans.factory.annotation.Autowired; @@ -60,12 +65,60 @@ public class OnlyOfficeWebService { @Autowired private OnlyOfficeService onlyOfficeService; - @GET @POST + @Path("/callback") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response postCallback( + @PathParam("fileId") String fileId, + CallbackVO callbackVO, + @Context HttpHeaders httpHeaders) { + log.debug("OnlyOffice REST post callback request for File ID: " + fileId); + logRequestHeaders(httpHeaders); + log.debug("OnlyOffice REST post callback " + callbackVO); + + if (!onlyOfficeModule.isEnabled()) { + return Response.serverError().status(Status.FORBIDDEN).build(); + } + + if (!onlyOfficeService.fileExists(fileId)) { + log.debug("File not found. File ID: " + fileId); + return Response.serverError().status(Status.NOT_FOUND).build(); + } + + CallbackResponseVO responseVO; + switch(callbackVO.getStatus()) { + case STATUS_READY_FOR_SAVING: + responseVO = updateContent(fileId, callbackVO); + break; + default: + // nothing to do + responseVO = success(); + } + + return Response.ok(responseVO).build(); + } + + private CallbackResponseVO updateContent(String fileId, CallbackVO callbackVO) { + String IdentityId = callbackVO.getUsers()[0]; + Identity identity = onlyOfficeService.getIdentity(IdentityId); + if (identity == null) { + return error(); + } + + boolean canUpdate = onlyOfficeService.canUpdateContent(fileId, identity); + if (!canUpdate) { + log.debug("Access has not right to update file. File ID: " + fileId + ", identity: " + IdentityId); + return error(); + } + boolean updated = onlyOfficeService.updateContent(fileId, identity, callbackVO.getUrl()); + return updated? success(): error(); + } + + @GET @Path("/contents") public Response getFile( @PathParam("fileId") String fileId, - @QueryParam("access_token") String accessToken, @Context HttpHeaders httpHeaders) { log.debug("OnlyOffice REST get file contents request for File ID: " + fileId); logRequestHeaders(httpHeaders); @@ -79,12 +132,6 @@ public class OnlyOfficeWebService { return Response.serverError().status(Status.NOT_FOUND).build(); } - Access access = onlyOfficeService.getAccess(accessToken); - if (access == null) { - log.debug("No access for token. File ID: " + fileId + ", token: " + accessToken); - return Response.serverError().status(Status.UNAUTHORIZED).build(); - } - File file = onlyOfficeService.getFile(fileId); return Response .ok(file) @@ -95,7 +142,7 @@ public class OnlyOfficeWebService { private void logRequestHeaders(HttpHeaders httpHeaders) { if (log.isDebug()) { - log.debug("WOPI Resquest headers:"); + log.debug("REST Resquest headers:"); for (Entry<String, List<String>> entry : httpHeaders.getRequestHeaders().entrySet()) { String name = entry.getKey(); String value = entry.getValue().stream().collect(Collectors.joining(", ")); diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/ApiConfigBuilder.java b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/ApiConfigBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..a6d9eff81eb6137f2d7bfbc8799ece4047aed6b2 --- /dev/null +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/ApiConfigBuilder.java @@ -0,0 +1,468 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.core.commons.services.doceditor.onlyoffice.ui; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeService; +import org.olat.core.commons.services.vfs.VFSMetadata; +import org.olat.core.helpers.Settings; +import org.olat.core.id.Identity; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.FileUtils; +import org.olat.restapi.security.RestSecurityHelper; +import org.olat.user.UserManager; +import org.springframework.beans.factory.annotation.Autowired; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * + * Initial date: 15 Apr 2019<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class ApiConfigBuilder { + + private static final OLog log = Tracing.createLoggerFor(ApiConfigBuilder.class); + + private final VFSMetadata vfsMetadata; + private final Identity identity; + private boolean edit; + + private static ObjectMapper mapper = new ObjectMapper(); + + @Autowired + private OnlyOfficeService onlyOfficeService; + @Autowired + private UserManager userManager; + + private ApiConfigBuilder(VFSMetadata vfsMetadata, Identity identity) { + this.vfsMetadata = vfsMetadata; + this.identity = identity; + CoreSpringFactory.autowireObject(this); + } + + public static ApiConfigBuilder builder(VFSMetadata vfsMetadata, Identity identity) { + return new ApiConfigBuilder(vfsMetadata, identity); + } + + /** + * Default: true + * + * @param edit + * @return + */ + public ApiConfigBuilder withEdit(boolean edit) { + this.edit = edit; + return this; + } + + public ApiConfig build() { + String fileName = vfsMetadata.getFilename(); + + ApiConfig apiConfig = new ApiConfig(); + apiConfig.setWidth("100%"); + apiConfig.setHeight("100%"); + apiConfig.setType("desktop"); + String suffix = FileUtils.getFileSuffix(fileName); + String documentType = onlyOfficeService.getEditorDocumentType(suffix); + apiConfig.setDocumentType(documentType); + + Document document = new Document(); + document.setFileType(suffix); + String key = onlyOfficeService.getDocumentKey(vfsMetadata); + document.setKey(key); + document.setTitle(fileName); + document.setUrl(getContentUrl()); + apiConfig.setDocument(document); + + Info info = new Info(); + String author = vfsMetadata.getAuthor() != null + ? userManager.getUserDisplayName(vfsMetadata.getAuthor()) + : null; + info.setAuthor(author); + info.setCreated(null); // not in metadata + info.setFolder(null); // is often a hidden folder, so we do not want to show + document.setInfo(info); + + Permissions permissions = new Permissions(); + permissions.setEdit(edit); + // Default is true. We do not want to restrict + permissions.setComment(null); + permissions.setDownload(null); + permissions.setFollForms(null); + permissions.setPrint(null); + permissions.setReview(null); + document.setPermissions(permissions); + + EditorConfig editorConfig = new EditorConfig(); + editorConfig.setCallbackUrl(getCallbackUrl()); + String mode = edit? "edit": "view"; + editorConfig.setMode(mode); + editorConfig.setLang(identity.getUser().getPreferences().getLanguage()); + apiConfig.setEditor(editorConfig); + + User user = new User(); + String name = userManager.getUserDisplayName(identity); + user.setName(name); + user.setId(identity.getKey().toString()); + editorConfig.setUser(user); + + return apiConfig; + } + + public String buildJson() { + try { + ApiConfig apiConfig = build(); + return mapper.writeValueAsString(apiConfig); + } catch (JsonProcessingException e) { + log.error("", e); + } + return null; + } + + private String getContentUrl() { + StringBuilder fileUrl = getFileUrl(); + fileUrl.append("contents"); + return fileUrl.toString(); + } + + private String getCallbackUrl() { + StringBuilder fileUrl = getFileUrl(); + fileUrl.append("callback"); + return fileUrl.toString(); + } + + private StringBuilder getFileUrl() { + StringBuilder fileUrl = new StringBuilder(); + fileUrl.append(Settings.getServerContextPathURI()); + fileUrl.append(RestSecurityHelper.SUB_CONTEXT); + fileUrl.append("/onlyoffice/files/"); + fileUrl.append(vfsMetadata.getUuid()); + fileUrl.append("/"); + return fileUrl; + } + + @JsonInclude(Include.NON_NULL) + public static class ApiConfig { + + private String token; + private String type; + private String documentType; + private String width; + private String height; + private Document document; + private EditorConfig editorConfig; + // not implemented yet + // private final Events events; + + private ApiConfig() { + // + } + + public String getToken() { + return token; + } + + private void setToken(String token) { + this.token = token; + } + + public String getType() { + return type; + } + + private void setType(String type) { + this.type = type; + } + + public String getDocumentType() { + return documentType; + } + + private void setDocumentType(String documentType) { + this.documentType = documentType; + } + + public String getWidth() { + return width; + } + + private void setWidth(String width) { + this.width = width; + } + + public String getHeight() { + return height; + } + + private void setHeight(String height) { + this.height = height; + } + + public Document getDocument() { + return document; + } + + private void setDocument(Document document) { + this.document = document; + } + + public EditorConfig getEditorConfig() { + return editorConfig; + } + + private void setEditor(EditorConfig editorConfig) { + this.editorConfig = editorConfig; + } + + } + + @JsonInclude(Include.NON_NULL) + public static class Document { + + private String fileType; + private String key; + private String title; + private String url; + private Info info; + private Permissions permissions; + + public String getFileType() { + return fileType; + } + + private void setFileType(String fileType) { + this.fileType = fileType; + } + + public String getKey() { + return key; + } + + private void setKey(String key) { + this.key = key; + } + + public String getTitle() { + return title; + } + + private void setTitle(String title) { + this.title = title; + } + + public String getUrl() { + return url; + } + + private void setUrl(String url) { + this.url = url; + } + + public Info getInfo() { + return info; + } + + private void setInfo(Info info) { + this.info = info; + } + + public Permissions getPermissions() { + return permissions; + } + + private void setPermissions(Permissions permissions) { + this.permissions = permissions; + } + + } + + @JsonInclude(Include.NON_NULL) + public static class Info { + + private String author; + private String created; + private String folder; + // not implemented yed + // private Object sharingSettings; + + public String getAuthor() { + return author; + } + + private void setAuthor(String author) { + this.author = author; + } + + public String getCreated() { + return created; + } + + private void setCreated(String created) { + this.created = created; + } + + public String getFolder() { + return folder; + } + + private void setFolder(String folder) { + this.folder = folder; + } + + } + + @JsonInclude(Include.NON_NULL) + public static class Permissions { + + private Boolean comment; + private Boolean download; + private Boolean edit; + private Boolean print; + private Boolean follForms; + private Boolean review; + + public Boolean getComment() { + return comment; + } + + private void setComment(Boolean comment) { + this.comment = comment; + } + + public Boolean getDownload() { + return download; + } + + private void setDownload(Boolean download) { + this.download = download; + } + + public Boolean getEdit() { + return edit; + } + + private void setEdit(Boolean edit) { + this.edit = edit; + } + + public Boolean getPrint() { + return print; + } + + private void setPrint(Boolean print) { + this.print = print; + } + + public Boolean getFollForms() { + return follForms; + } + + private void setFollForms(Boolean follForms) { + this.follForms = follForms; + } + + public Boolean getReview() { + return review; + } + + private void setReview(Boolean review) { + this.review = review; + } + + } + + @JsonInclude(Include.NON_NULL) + public static class EditorConfig { + + private String callbackUrl; + private String lang; + private String mode; + private User user; + // not implemented yet + // private Object recent; +// private String createUrl; + // private Customization costumisazion; + // private Embedded embedded; + // private Customization costumisazion; + + public String getCallbackUrl() { + return callbackUrl; + } + + private void setCallbackUrl(String callbackUrl) { + this.callbackUrl = callbackUrl; + } + + public String getLang() { + return lang; + } + + private void setLang(String lang) { + this.lang = lang; + } + + public String getMode() { + return mode; + } + + private void setMode(String mode) { + this.mode = mode; + } + + public User getUser() { + return user; + } + + private void setUser(User user) { + this.user = user; + } + + } + + @JsonInclude(Include.NON_NULL) + public static class User { + + private String id; + private String name; + + public String getId() { + return id; + } + + private void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + private void setName(String name) { + this.name = name; + } + + } +} diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeEditorController.java b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeEditorController.java index 1c526bee873b78c3c78e72fdb6a77bf8d00d8c63..45ca2514bc5ca0f03ec34ef2eb00ba5c0b763c29 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeEditorController.java +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeEditorController.java @@ -19,10 +19,9 @@ */ package org.olat.core.commons.services.doceditor.onlyoffice.ui; +import org.olat.core.commons.services.doceditor.DocEditor.Mode; import org.olat.core.commons.services.doceditor.DocEditorSecurityCallback; import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeModule; -import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeService; -import org.olat.core.commons.services.doceditor.wopi.Access; import org.olat.core.commons.services.vfs.VFSMetadata; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; @@ -30,6 +29,8 @@ import org.olat.core.gui.components.velocity.VelocityContainer; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; import org.olat.core.util.CodeHelper; import org.olat.core.util.vfs.VFSLeaf; import org.springframework.beans.factory.annotation.Autowired; @@ -42,20 +43,14 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class OnlyOfficeEditorController extends BasicController { - private final VFSLeaf vfsLeaf; - private DocEditorSecurityCallback secCallback; - private Access access; + private static final OLog log = Tracing.createLoggerFor(OnlyOfficeEditorController.class); @Autowired private OnlyOfficeModule onlyOfficeModule; - @Autowired - private OnlyOfficeService onlyOfficeService; public OnlyOfficeEditorController(UserRequest ureq, WindowControl wControl, VFSLeaf vfsLeaf, final DocEditorSecurityCallback securityCallback) { super(ureq, wControl); - this.vfsLeaf = vfsLeaf; - this.secCallback = securityCallback; VelocityContainer mainVC = createVelocityContainer("editor"); @@ -63,12 +58,18 @@ public class OnlyOfficeEditorController extends BasicController { if (vfsMetadata == null) { mainVC.contextPut("warning", translate("editor.warning.no.metadata")); } else { - this.access = onlyOfficeService.createAccess(vfsMetadata, getIdentity(), secCallback); - mainVC.contextPut("key", access.getToken()); - mainVC.contextPut("fileId", access.getFileId()); - mainVC.contextPut("accessToken", access.getToken()); - mainVC.contextPut("id", "o_" + CodeHelper.getRAMUniqueID()); - mainVC.contextPut("apiUrl", onlyOfficeModule.getApiUrl()); + String apiConfig = ApiConfigBuilder.builder(vfsMetadata, getIdentity()) + .withEdit(Mode.EDIT.equals(securityCallback.getMode())) + .buildJson(); + log.debug("OnlyOffice ApiConfig: " + apiConfig); + + if (apiConfig == null) { + mainVC.contextPut("warning", translate("editor.warning.no.api.configs")); + } else { + mainVC.contextPut("id", "o_" + CodeHelper.getRAMUniqueID()); + mainVC.contextPut("apiUrl", onlyOfficeModule.getApiUrl()); + mainVC.contextPut("apiConfig", apiConfig); + } } putInitialPanel(mainVC); @@ -81,7 +82,7 @@ public class OnlyOfficeEditorController extends BasicController { @Override protected void doDispose() { - onlyOfficeService.deleteAccess(access); + // } } diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_content/editor.html b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_content/editor.html index 870f9dcf403507bf74a89c035c53971fc8bdcdb0..49cd1cd059914d7195b4bcb4e3705d6ac527996b 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_content/editor.html +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_content/editor.html @@ -9,17 +9,7 @@ <script type="text/javascript"> /* <![CDATA[ */ jQuery('$id').ready(function(){ - new DocsAPI.DocEditor("$id", { - "document": { - "fileType": "docx", - "key": "$key", - "title": "Example Document Title.docx", - "url": "https://limmat.frentix.com/olat/restapi/onlyoffice/files/$fileId/contents?access_token=$accessToken" - }, - "width": "100%", - "height": "100%", - "documentType": "text" - }); + new DocsAPI.DocEditor("$id", $apiConfig); }); /* ]]> */ </script> diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_i18n/LocalStrings_de.properties index ad0d23dd8d78c73f1c7b8fde274e9e7136d45836..e9f5610c60230796aa0ab19cb5ee42f584cfb357 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_i18n/LocalStrings_de.properties @@ -5,4 +5,5 @@ admin.enabled=Modul "OnlyOffice" admin.title=OnlyOffice editor.display.name=OnlyOffice editor.warning.locked=Das Dokument wird bereits in einem anderen Editor bearbeitet und kann deshalb in OnylOffice nicht bearbeitet werden. +editor.warning.no.api.config=Dieses Dokument kann nicht angezeigt werden! editor.warning.no.metadata=Dieses Dokument kann nicht angezeigt werden! \ No newline at end of file diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_i18n/LocalStrings_en.properties index b432cad93c13fedf394edc063c62cfee1716c980..5b5a0e42c58bdb721e579b287de82ea32f9f8f95 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_i18n/LocalStrings_en.properties @@ -5,4 +5,5 @@ admin.enabled=Module "OnylOffice" admin.title=OnlyOffice editor.display.name=OnlyOffice editor.warning.locked=That document is already edited in another editor and therefore it is displayed in a read-onyl view. +editor.warning.no.api.config=It is not possible to display that document. editor.warning.no.metadata=It is not possible to display that document. \ No newline at end of file