diff --git a/frontend-js/src/main/js/ConfigurationType.js b/frontend-js/src/main/js/ConfigurationType.js index 183411fcea0dadee718eb8984c9874866e201534..5ebf5f1e7d5148b7b7a0fd321f1a4ff3032fe41b 100644 --- a/frontend-js/src/main/js/ConfigurationType.js +++ b/frontend-js/src/main/js/ConfigurationType.js @@ -18,6 +18,7 @@ var ConfigurationType = { SHOW_REACTION_TYPE: "SHOW_REACTION_TYPE", SEARCH_DISTANCE: "SEARCH_DISTANCE", SEARCH_RESULT_NUMBER: "SEARCH_RESULT_NUMBER", + TERMS_OF_USE: "TERMS_OF_USE", USER_MANUAL_FILE: "USER_MANUAL_FILE" }; diff --git a/frontend-js/src/main/js/ServerConnector.js b/frontend-js/src/main/js/ServerConnector.js index 9f062cf988f167ccb5ee7d059c27f9ae1285ef2c..6efe146936336b6e509c892f3ba619f76c971ba3 100644 --- a/frontend-js/src/main/js/ServerConnector.js +++ b/frontend-js/src/main/js/ServerConnector.js @@ -1007,7 +1007,7 @@ ServerConnector.getUser = function (login) { login: login }; var filterParams = { - columns: ["id", "login", "name", "surname", "email", "minColor", "maxColor", "neutralColor", "simpleColor", "removed", "privileges", "preferences"] + columns: ["id", "login", "name", "surname", "email", "minColor", "maxColor", "neutralColor", "simpleColor", "removed", "privileges", "preferences", "termsOfUseConsent"] }; return self.sendGetRequest(self.getUserUrl(queryParams, filterParams)).then(function (content) { @@ -1024,6 +1024,11 @@ ServerConnector.getUser = function (login) { }); }; +/** + * + * @param {User} user + * @returns {PromiseLike<any>} + */ ServerConnector.updateUser = function (user) { var self = this; var queryParams = { @@ -1034,7 +1039,8 @@ ServerConnector.updateUser = function (user) { name: user.getName(), surname: user.getSurname(), password: user.getPassword(), - email: user.getEmail() + email: user.getEmail(), + termsOfUseConsent: user.isTermsOfUseConsent() } }; return self.sendPatchRequest(self.getUserUrl(queryParams), filterParams).then(function () { diff --git a/frontend-js/src/main/js/gui/AddOverlayDialog.js b/frontend-js/src/main/js/gui/AddOverlayDialog.js index 8d730471ae36be7564ed0b13bdb6f4778a0c8c91..d0813b9624be787788320e2cdaf254f19f10bbfb 100644 --- a/frontend-js/src/main/js/gui/AddOverlayDialog.js +++ b/frontend-js/src/main/js/gui/AddOverlayDialog.js @@ -73,6 +73,13 @@ AddOverlayDialog.prototype.createGui = function () { content.appendChild(contentInput); content.appendChild(guiUtils.createNewLine()); + var consentCheckbox = Functions.createElement({type:"input", name:"overlay-google-consent"}); + consentCheckbox.type = "checkbox"; + content.appendChild(consentCheckbox); + content.appendChild(guiUtils.createLabel("I am aware that this map is displayed under the terms of the <a href='https://cloud.google.com/maps-platform/terms/' target='_blank'>license of Google Maps Platform</a> and I agree to these terms. " + + "In particular, I warrant that this dataset does not contain Protected Health Information (as defined in and subject to HIPAA). ")); + content.appendChild(guiUtils.createNewLine()); + self.getElement().appendChild(content); }; @@ -154,6 +161,7 @@ AddOverlayDialog.prototype.addOverlay = function () { var nameInput = $("[name='overlay-name']", self.getElement())[0]; var descriptionInput = $("[name='overlay-description']", self.getElement())[0]; var filename = $("[name='overlay-file']", self.getElement())[0].value; + var consent = $("[name='overlay-google-consent']", self.getElement()).is(":checked"); var overlay = new DataOverlay({ name: nameInput.value, description: descriptionInput.value, @@ -170,7 +178,8 @@ AddOverlayDialog.prototype.addOverlay = function () { return ServerConnector.addOverlay({ fileId: file.id, overlay: overlay, - projectId: self.getProject().getProjectId() + projectId: self.getProject().getProjectId(), + googleLicenseConsent: consent }); }).then(function (result) { overlay = result; diff --git a/frontend-js/src/main/js/map/data/User.js b/frontend-js/src/main/js/map/data/User.js index 65abd561260675f63b37cec1d6013d4e6059f4db..6128f56bbf5aff332df5dacfe0e804bd88083f59 100644 --- a/frontend-js/src/main/js/map/data/User.js +++ b/frontend-js/src/main/js/map/data/User.js @@ -24,6 +24,7 @@ function User(javaObject) { this.setMaxColor(javaObject.maxColor); this.setNeutralColor(javaObject.neutralColor); this.setSimpleColor(javaObject.simpleColor); + this.setTermsOfUseConsent(javaObject.termsOfUseConsent); } // this class inherits from ObjectWithListeners class where generic methods for @@ -216,4 +217,12 @@ User.prototype.update = function (user) { return self.callListeners("onreload"); }; +User.prototype.setTermsOfUseConsent = function(termsOfUseConsent) { + this._termsOfUseConsent = termsOfUseConsent; +}; + +User.prototype.isTermsOfUseConsent = function() { + return this._termsOfUseConsent; +}; + module.exports = User; diff --git a/frontend-js/src/main/js/minerva.js b/frontend-js/src/main/js/minerva.js index a97d50545bab049b7c03a6cb3a93d8496438e198..92dac01dcd42c64326b777842c1cd737407e7a00 100644 --- a/frontend-js/src/main/js/minerva.js +++ b/frontend-js/src/main/js/minerva.js @@ -64,6 +64,60 @@ function processUrlGetParams(params) { } +/** + * + * @param {User} user + * @param {string} termsOfUseUrl + */ +function requestConsent(user, termsOfUseUrl) { + var dialog = document.createElement("div"); + var dialogContent = document.createElement("div"); + dialogContent.appendChild(functions.createElement({ + type: "span", + content: "I agree to the minerva <a href='" + termsOfUseUrl + "' target='_blank'>Terms of Use</a>." + })); + var checkbox = functions.createElement({type: "input", inputType: "checkbox", style: "float: left"}); + var okButton = functions.createElement({ + type: "button", + content: "OK", + className: "ui-button ui-corner-all ui-widget", + onclick: function () { + user.setTermsOfUseConsent($(checkbox).is(':checked')); + return ServerConnector.updateUser(user).then(function () { + $(dialog).dialog("close"); + }).catch(GuiConnector.alert); + } + }); + var cancelButton = functions.createElement({ + type: "button", + content: "I disagree", + className: "ui-button ui-corner-all ui-widget", + onclick: function () { + return ServerConnector.logout().catch(GuiConnector.alert); + } + }); + $(okButton).prop("disabled", true); + $(checkbox).change(function () { + $(okButton).prop("disabled", !$(checkbox).is(':checked')); + }); + dialogContent.appendChild(checkbox); + dialogContent.appendChild(okButton); + dialogContent.appendChild(cancelButton); + + dialog.appendChild(dialogContent); + document.body.appendChild(dialog); + $(dialog).dialog({ + classes: { + "ui-dialog": "ui-state-error" + }, + modal: true, + closeOnEscape: false, + title: "Terms of Use" + }).siblings('.ui-dialog-titlebar').css("background", "red"); + $(".ui-dialog-titlebar-close", $(dialog).dialog().siblings('.ui-dialog-titlebar')).hide(); + $(dialog).dialog("open"); +} + function insertGoogleAnalyticsCode() { return ServerConnector.getConfigurationParam(ConfigurationType.GOOGLE_ANALYTICS_IDENTIFIER).then(function (identifier) { if (identifier === "" || identifier === undefined || identifier === null) { @@ -360,9 +414,14 @@ function create(params) { } return Promise.all(promises); }).then(function () { + return ServerConnector.getLoggedUser(); + }).then(function (user) { if (leftPanel.isGoogleLicenseConsentRequired()) { GuiConnector.alert("Some data overlays doesn't have consent to the terms of the <a href='https://cloud.google.com/maps-platform/terms/' target='_blank'>license of Google Maps Platform</a>. To be able to visualize them you must edit data overlay. ") } + if (user.getLogin() !== "anonymous" && !user.isTermsOfUseConsent()) { + requestConsent(user, params.getConfiguration().getOption(ConfigurationType.TERMS_OF_USE)); + } var result = { destroy: function () { return leftPanel.destroy().then(function () { diff --git a/model/src/main/java/lcsb/mapviewer/model/user/ConfigurationElementType.java b/model/src/main/java/lcsb/mapviewer/model/user/ConfigurationElementType.java index 27b2aceee9fb34686cb5d703814b1a9551e3c427..835fcc4f7203c0ce4e6354f1a9264abe64d21345 100644 --- a/model/src/main/java/lcsb/mapviewer/model/user/ConfigurationElementType.java +++ b/model/src/main/java/lcsb/mapviewer/model/user/ConfigurationElementType.java @@ -194,6 +194,11 @@ public enum ConfigurationElementType { + "(\"General overlays\") on this MINERVA server contain Protected Health Information (as defined in and subject to HIPAA).", "", ConfigurationElementEditType.STRING, false, ConfigurationElementTypeGroup.SERVER_CONFIGURATION), + /** + * File where legend 4/4 is stored. + */ + TERMS_OF_USE("Terms of use file", "resources/other/terms_of_use.pdf", ConfigurationElementEditType.URL, false, ConfigurationElementTypeGroup.LEGEND_AND_LOGO), + ; /** diff --git a/model/src/main/java/lcsb/mapviewer/model/user/User.java b/model/src/main/java/lcsb/mapviewer/model/user/User.java index 744af477513acf882220c9f23128dbfb77733e78..fdb5e080a10f840dda1a19bc7330acf051c72602 100644 --- a/model/src/main/java/lcsb/mapviewer/model/user/User.java +++ b/model/src/main/java/lcsb/mapviewer/model/user/User.java @@ -31,7 +31,7 @@ import lcsb.mapviewer.common.comparator.StringComparator; public class User implements Serializable { /** - * + * */ private static final long serialVersionUID = 1L; @@ -118,6 +118,9 @@ public class User implements Serializable { */ private boolean removed = false; + @Column(name="terms_of_use_consent") + private boolean termsOfUseConsent = false; + /** * Set of user privileges. */ @@ -139,7 +142,7 @@ public class User implements Serializable { /** * Adds privilege to the user. - * + * * @param privilege * privilege to add */ @@ -367,4 +370,12 @@ public class User implements Serializable { this.neutralColor = neutralColor; } + public boolean isTermsOfUseConsent() { + return termsOfUseConsent; + } + + public void setTermsOfUseConsent(boolean termsOfUseConsent) { + this.termsOfUseConsent = termsOfUseConsent; + } + } diff --git a/persist/src/db/11.1.0/fix_db_20180525.sql b/persist/src/db/11.1.0/fix_db_20180525.sql new file mode 100644 index 0000000000000000000000000000000000000000..70350604e3100f5731f53e5b0162df1098da67f7 --- /dev/null +++ b/persist/src/db/11.1.0/fix_db_20180525.sql @@ -0,0 +1 @@ +alter table user_table add column terms_of_use_consent boolean default false; diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayController.java b/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayController.java index 02c676e99a510cff2f83efa3a03f388379fc7ace..fb2f9e247b1eddfd6f241563c8e4c8a2ad9510b3 100644 --- a/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayController.java +++ b/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayController.java @@ -106,9 +106,10 @@ public class OverlayController extends BaseController { @RequestParam(value = "content", defaultValue = "") String content, // @RequestParam(value = "fileId", defaultValue = "") String fileId, // @RequestParam(value = "filename") String filename, // + @RequestParam(value = "googleLicenseConsent") String googleLicenseConsent, // @RequestParam(value = "type", defaultValue = "") String type // ) throws SecurityException, QueryException, IOException { - return overlayRestImp.addOverlay(token, projectId, name, description, content, fileId, filename, type); + return overlayRestImp.addOverlay(token, projectId, name, description, content, fileId, filename, type, googleLicenseConsent); } @RequestMapping(value = "/projects/{projectId}/overlays/{overlayId}", method = { RequestMethod.DELETE }, produces = { diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayRestImpl.java b/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayRestImpl.java index 9e4148389169025792ace2fc548f5a61e9e41bfb..1e5ac46b63110b119b51c45cf034ba33e17908e0 100644 --- a/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayRestImpl.java +++ b/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayRestImpl.java @@ -314,7 +314,7 @@ public class OverlayRestImpl extends BaseRestImpl { } public Map<String, Object> addOverlay(String token, String projectId, String name, String description, String content, - String fileId, String filename, String type) throws SecurityException, QueryException, IOException { + String fileId, String filename, String type, String googleLicenseConsent) throws SecurityException, QueryException, IOException { User user = getUserService().getUserByToken(token); if (Configuration.ANONYMOUS_LOGIN.equals(user.getLogin())) { throw new SecurityException("You have no privileges to add overlay"); @@ -357,7 +357,7 @@ public class OverlayRestImpl extends BaseRestImpl { Layout layout = layoutService.createLayout(new CreateLayoutParams().async(false).colorInputStream(stream) .description(description).layoutFileName(filename).model(model).name(name).user(user) - .colorSchemaType(colorSchemaType).directory(".")); + .colorSchemaType(colorSchemaType).directory(".").googleLicenseConsent(googleLicenseConsent.equalsIgnoreCase("true"))); int count = layoutService.getCustomLayouts(model, token, false, user).size(); layout.setOrderIndex(count); diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/users/UserRestImpl.java b/rest-api/src/main/java/lcsb/mapviewer/api/users/UserRestImpl.java index aa64c6491b495b3aa77b6bc473fa8424d00772d5..de838df959d2569ba49fe26d57fc0f12c666c7fd 100644 --- a/rest-api/src/main/java/lcsb/mapviewer/api/users/UserRestImpl.java +++ b/rest-api/src/main/java/lcsb/mapviewer/api/users/UserRestImpl.java @@ -81,6 +81,7 @@ public class UserRestImpl extends BaseRestImpl { columnsSet.add("simpleColor"); columnsSet.add("removed"); columnsSet.add("privileges"); + columnsSet.add("termsOfUseConsent"); } else { for (String str : columns.split(",")) { columnsSet.add(str); @@ -114,6 +115,8 @@ public class UserRestImpl extends BaseRestImpl { value = user.getSimpleColor(); } else if (column.equals("removed")) { value = user.isRemoved(); + } else if (column.equals("termsofuseconsent")) { + value = user.isTermsOfUseConsent(); } else if (column.equals("privileges") && admin) { value = preparePrivileges(user); } else if (column.equals("preferences")) { @@ -149,7 +152,7 @@ public class UserRestImpl extends BaseRestImpl { /** * Prepares annotator parameters in the form of a map having annotators class * names as keys and map of name:value pairs of given annotator as values. - * + * * @param annotatorsParams * @return */ @@ -537,6 +540,8 @@ public class UserRestImpl extends BaseRestImpl { user.setSurname(stringValue); } else if (key.equalsIgnoreCase("email")) { user.setEmail(stringValue); + } else if (key.equalsIgnoreCase("termsofuseconsent")) { + user.setTermsOfUseConsent((Boolean) value); } else if (key.equalsIgnoreCase("password")) { if (stringValue != null && !stringValue.trim().isEmpty()) { user.setCryptedPassword(getUserService().encodePassword(stringValue)); diff --git a/web/src/main/webapp/resources/other/terms_of_use.pdf b/web/src/main/webapp/resources/other/terms_of_use.pdf new file mode 100644 index 0000000000000000000000000000000000000000..21f7fb0abbde215ce1391ed614874e4060bcb94c Binary files /dev/null and b/web/src/main/webapp/resources/other/terms_of_use.pdf differ diff --git a/web/src/main/webapp/resources/other/user_guide.pdf b/web/src/main/webapp/resources/other/user_guide.pdf index 6f4d57c5108c5da7993f9184d8300101f57b0153..84030df5fa349e3ba56d6253110297473e85ef98 100644 Binary files a/web/src/main/webapp/resources/other/user_guide.pdf and b/web/src/main/webapp/resources/other/user_guide.pdf differ