diff --git a/docs/manual/docs/administrator-guide/configuring-the-catalog/img/application-feedback-link.png b/docs/manual/docs/administrator-guide/configuring-the-catalog/img/application-feedback-link.png new file mode 100644 index 00000000000..a52132a35dd Binary files /dev/null and b/docs/manual/docs/administrator-guide/configuring-the-catalog/img/application-feedback-link.png differ diff --git a/docs/manual/docs/administrator-guide/configuring-the-catalog/img/application-feedback.png b/docs/manual/docs/administrator-guide/configuring-the-catalog/img/application-feedback.png new file mode 100644 index 00000000000..fa54aa2317b Binary files /dev/null and b/docs/manual/docs/administrator-guide/configuring-the-catalog/img/application-feedback.png differ diff --git a/docs/manual/docs/administrator-guide/configuring-the-catalog/system-configuration.md b/docs/manual/docs/administrator-guide/configuring-the-catalog/system-configuration.md index 8f9f567d1e4..f9dfb053d0e 100644 --- a/docs/manual/docs/administrator-guide/configuring-the-catalog/system-configuration.md +++ b/docs/manual/docs/administrator-guide/configuring-the-catalog/system-configuration.md @@ -94,12 +94,15 @@ Enable the self registration form. See [User Self-Registration](../managing-user You can configure optionally re-Captcha, to protect you and your users from spam and abuse. And a list of email domains (separated by commas) that can request an account. If not configured any email address is allowed. -## system/userFeedback +## User application feedback -!!! warning "Deprecated" +Enabling the setting, displays in the application footer a link to a page that allows sending comments about the application. + +![](img/application-feedback-link.png) - 3.0.0 +![](img/application-feedback.png) +It requires an email server configured. ## Link in metadata records diff --git a/services/src/main/java/org/fao/geonet/api/site/SiteApi.java b/services/src/main/java/org/fao/geonet/api/site/SiteApi.java index dcefa7dd5e0..23db24a76fe 100644 --- a/services/src/main/java/org/fao/geonet/api/site/SiteApi.java +++ b/services/src/main/java/org/fao/geonet/api/site/SiteApi.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2001-2023 Food and Agriculture Organization of the + * Copyright (C) 2001-2024 Food and Agriculture Organization of the * United Nations (FAO-UN), United Nations World Food Programme (WFP) * and United Nations Environment Programme (UNEP) * @@ -43,10 +43,12 @@ import org.fao.geonet.api.ApiParams; import org.fao.geonet.api.ApiUtils; import org.fao.geonet.api.OpenApiConfig; +import org.fao.geonet.api.exception.FeatureNotEnabledException; import org.fao.geonet.api.exception.NotAllowedException; import org.fao.geonet.api.site.model.SettingSet; import org.fao.geonet.api.site.model.SettingsListResponse; import org.fao.geonet.api.tools.i18n.LanguageUtils; +import org.fao.geonet.api.users.recaptcha.RecaptchaChecker; import org.fao.geonet.constants.Geonet; import org.fao.geonet.doi.client.DoiManager; import org.fao.geonet.domain.*; @@ -69,6 +71,7 @@ import org.fao.geonet.repository.*; import org.fao.geonet.repository.specification.MetadataSpecs; import org.fao.geonet.resources.Resources; +import org.fao.geonet.util.MailUtil; import org.fao.geonet.utils.FilePathChecker; import org.fao.geonet.utils.Log; import org.fao.geonet.utils.ProxyInfo; @@ -78,18 +81,12 @@ import org.springframework.http.HttpEntity; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.*; import javax.imageio.ImageIO; -import javax.servlet.ServletRegistration; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.awt.image.BufferedImage; @@ -101,19 +98,12 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Optional; -import java.util.TimeZone; +import java.util.*; import static org.apache.commons.fileupload.util.Streams.checkFileName; import static org.fao.geonet.api.ApiParams.API_CLASS_CATALOG_TAG; import static org.fao.geonet.constants.Geonet.Path.IMPORT_STYLESHEETS_SCHEMA_PREFIX; +import static org.fao.geonet.kernel.setting.Settings.SYSTEM_FEEDBACK_EMAIL; /** * @@ -903,4 +893,77 @@ public List getXslTransformations( return list; } } + + + @io.swagger.v3.oas.annotations.Operation( + summary = "Send an email to catalogue administrator with feedback about the application", + description = "") + @PostMapping( + value = "/userfeedback", + produces = MediaType.APPLICATION_JSON_VALUE + ) + @ResponseStatus(HttpStatus.CREATED) + @ResponseBody + public ResponseEntity sendApplicationUserFeedback( + @Parameter( + description = "Recaptcha validation key." + ) + @RequestParam(required = false, defaultValue = "") final String recaptcha, + @Parameter( + description = "User name.", + required = true + ) + @RequestParam final String name, + @Parameter( + description = "User organisation.", + required = true + ) + @RequestParam final String org, + @Parameter( + description = "User email address.", + required = true + ) + @RequestParam final String email, + @Parameter( + description = "A comment or question.", + required = true + ) + @RequestParam final String comments, + @Parameter(hidden = true) final HttpServletRequest request + ) throws Exception { + Locale locale = languageUtils.parseAcceptLanguage(request.getLocales()); + ResourceBundle messages = ResourceBundle.getBundle("org.fao.geonet.api.Messages", locale); + + boolean feedbackEnabled = settingManager.getValueAsBool(Settings.SYSTEM_USERFEEDBACK_ENABLE, false); + if (!feedbackEnabled) { + throw new FeatureNotEnabledException( + "Application feedback is not enabled.") + .withMessageKey("exception.resourceNotEnabled.applicationFeedback") + .withDescriptionKey("exception.resourceNotEnabled.applicationFeedback.description"); + } + + boolean recaptchaEnabled = settingManager.getValueAsBool(Settings.SYSTEM_USERSELFREGISTRATION_RECAPTCHA_ENABLE); + + if (recaptchaEnabled) { + boolean validRecaptcha = RecaptchaChecker.verify(recaptcha, + settingManager.getValue(Settings.SYSTEM_USERSELFREGISTRATION_RECAPTCHA_SECRETKEY)); + if (!validRecaptcha) { + return new ResponseEntity<>( + messages.getString("recaptcha_not_valid"), HttpStatus.PRECONDITION_FAILED); + } + } + + String to = settingManager.getValue(SYSTEM_FEEDBACK_EMAIL); + + Set toAddress = new HashSet<>(); + toAddress.add(to); + + MailUtil.sendMail(new ArrayList<>(toAddress), + messages.getString("site_user_feedback_title"), + String.format( + messages.getString("site_user_feedback_text"), + name, email, org, comments), + settingManager); + return new ResponseEntity<>(HttpStatus.CREATED); + } } diff --git a/web-ui/src/main/resources/catalog/components/contactus/ContactUsDirective.js b/web-ui/src/main/resources/catalog/components/contactus/ContactUsDirective.js index 1eed76abf55..10df5d42a19 100644 --- a/web-ui/src/main/resources/catalog/components/contactus/ContactUsDirective.js +++ b/web-ui/src/main/resources/catalog/components/contactus/ContactUsDirective.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2001-2016 Food and Agriculture Organization of the + * Copyright (C) 2001-2024 Food and Agriculture Organization of the * United Nations (FAO-UN), United Nations World Food Programme (WFP) * and United Nations Environment Programme (UNEP) * @@ -31,28 +31,81 @@ */ module.directive("gnContactUsForm", [ "$http", - function ($http) { + "$translate", + "vcRecaptchaService", + "gnConfigService", + "gnConfig", + function ($http, $translate, vcRecaptchaService, gnConfigService, gnConfig) { return { restrict: "A", replace: true, scope: { user: "=" }, - templateUrl: "../../catalog/components/share/" + "partials/contactusform.html", + templateUrl: + "../../catalog/components/contactus/" + "partials/contactusform.html", link: function (scope, element, attrs) { - scope.send = function (formId) { - $http({ - url: "contact.send@json", - method: "POST", - data: $(formId).serialize(), - headers: { "Content-Type": "application/x-www-form-urlencoded" } - }).then(function (response) { - // TODO: report no email sent - if (response.status === 200) { - scope.success = true; - } else { + gnConfigService.load().then(function (c) { + scope.recaptchaEnabled = + gnConfig["system.userSelfRegistration.recaptcha.enable"]; + scope.recaptchaKey = + gnConfig["system.userSelfRegistration.recaptcha.publickey"]; + }); + + scope.resolveRecaptcha = false; + + function initModel() { + scope.feedbackModel = { + name: scope.user.name, + email: scope.user.email, + org: "", + comments: "" + }; + } + + initModel(); + + scope.send = function (form, formId) { + if (scope.recaptchaEnabled) { + if (vcRecaptchaService.getResponse() === "") { + scope.resolveRecaptcha = true; + + var deferred = $q.defer(); + deferred.resolve(""); + return deferred.promise; } - }); + scope.resolveRecaptcha = false; + scope.captcha = vcRecaptchaService.getResponse(); + $("#recaptcha").val(scope.captcha); + } + + if (form.$valid) { + $http({ + url: "../api/site/userfeedback", + method: "POST", + data: $(formId).serialize(), + headers: { + "Content-Type": "application/x-www-form-urlencoded" + } + }).then( + function (response) { + scope.$emit("StatusUpdated", { + msg: $translate.instant("feebackSent"), + timeout: 2, + type: "info" + }); + initModel(); + }, + function (response) { + scope.success = false; + scope.$emit("StatusUpdated", { + msg: $translate.instant("feebackSentError"), + timeout: 0, + type: "danger" + }); + } + ); + } }; } }; diff --git a/web-ui/src/main/resources/catalog/components/contactus/partials/contactusform.html b/web-ui/src/main/resources/catalog/components/contactus/partials/contactusform.html index 716d5e9dc26..059b1bcd449 100644 --- a/web-ui/src/main/resources/catalog/components/contactus/partials/contactusform.html +++ b/web-ui/src/main/resources/catalog/components/contactus/partials/contactusform.html @@ -1,31 +1,74 @@ -
+ -
- +
+
- +
-
- +
+
- +
-
- +
+
- +
-
- +
+
-
- -

feebackSent

diff --git a/web-ui/src/main/resources/catalog/js/CatController.js b/web-ui/src/main/resources/catalog/js/CatController.js index fd7963fc39f..da955a014c2 100644 --- a/web-ui/src/main/resources/catalog/js/CatController.js +++ b/web-ui/src/main/resources/catalog/js/CatController.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2001-2016 Food and Agriculture Organization of the + * Copyright (C) 2001-2024 Food and Agriculture Organization of the * United Nations (FAO-UN), United Nations World Food Programme (WFP) * and United Nations Environment Programme (UNEP) * @@ -1608,6 +1608,10 @@ return gnGlobalSettings.gnCfg.mods.footer.showApplicationInfoAndLinksInFooter; }; + $scope.getContactusVisible = function () { + return gnConfig[gnConfig.key.isFeedbackEnabled]; + }; + function detectNode(detector) { if (detector.regexp) { var res = new RegExp(detector.regexp).exec(location.pathname); diff --git a/web-ui/src/main/resources/catalog/js/ContactUsController.js b/web-ui/src/main/resources/catalog/js/ContactUsController.js index 18876c80ca7..f510439db18 100644 --- a/web-ui/src/main/resources/catalog/js/ContactUsController.js +++ b/web-ui/src/main/resources/catalog/js/ContactUsController.js @@ -26,9 +26,17 @@ goog.require("gn_contactus_directive"); - var module = angular.module("gn_contact_us_controller", ["gn_contactus_directive"]); + var module = angular.module("gn_contact_us_controller", [ + "gn_contactus_directive", + "vcRecaptcha" + ]); - module.constant("$LOCALES", ["core"]); + module.config([ + "$LOCALES", + function ($LOCALES) { + $LOCALES.push("/../api/i18n/packages/search"); + } + ]); /** * diff --git a/web-ui/src/main/resources/catalog/locales/en-core.json b/web-ui/src/main/resources/catalog/locales/en-core.json index ef482004ea7..68ec80ffe08 100644 --- a/web-ui/src/main/resources/catalog/locales/en-core.json +++ b/web-ui/src/main/resources/catalog/locales/en-core.json @@ -126,6 +126,7 @@ "featureCatalog": "Feature catalog", "frequency": "Frequency", "feebackSent": "Your message has been sent to the catalog manager.", + "feebackSentError": "An error occurred sending your to the catalog manager. Please try again later, contact the service provider, or report this issue.", "feedbackNotEnable": "Feedback is not enabled.", "filter": "Filter", "filterSearch": "Display search options", diff --git a/web-ui/src/main/resources/catalog/views/default/less/gn_contact_us_default.less b/web-ui/src/main/resources/catalog/views/default/less/gn_contact_us_default.less new file mode 100644 index 00000000000..d5c88c43aed --- /dev/null +++ b/web-ui/src/main/resources/catalog/views/default/less/gn_contact_us_default.less @@ -0,0 +1,15 @@ +@import "../../../style/gn_contact_us.less"; + +// nojs styles +.gn-nojs { + .gn-top-search { + padding: 30px 0; + margin-bottom: 30px; + .gn-form-any input.input-lg { + height: 46px; + } + .btn-lg { + padding: 13px 16px; + } + } +} diff --git a/web-ui/src/main/resources/catalog/views/default/templates/footer.html b/web-ui/src/main/resources/catalog/views/default/templates/footer.html index 018b6eedb41..eb006285b99 100644 --- a/web-ui/src/main/resources/catalog/views/default/templates/footer.html +++ b/web-ui/src/main/resources/catalog/views/default/templates/footer.html @@ -12,6 +12,11 @@ about +
  • + + contact + +