diff --git a/Pipfile.lock b/Pipfile.lock
index defcfec2..fa5c7657 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -769,7 +769,7 @@
"sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216",
"sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05"
],
- "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
"version": "==1.0.0"
},
"geojson": {
@@ -983,7 +983,7 @@
"sha256:afd7bd0d2a97bf3e2d415ad70031292d99a36e124e5a57e42cdbe803a0d77ef6",
"sha256:c7b07de416fe6fe6a789c7f52a2532560e0f80fc27e9fe90e5d4386c3b6ff63b"
],
- "markers": "python_version >= '3.7'",
+ "index": "pypi",
"version": "==12.0.0b3.dev3"
},
"invenio-assets": {
@@ -1113,7 +1113,7 @@
"sha256:2b3d849f32f9bfd1aa636d951d952b6df2fa2b85a350080dd5a90e3ff7e56592",
"sha256:7537b9902171517208552893eea3efb76fb3b55558671ea4ea6fd218d4fbc89b"
],
- "markers": "python_version >= '3.8'",
+ "index": "pypi",
"version": "==2.1.0"
},
"invenio-mail": {
@@ -1334,7 +1334,6 @@
"sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"
],
"index": "pypi",
- "markers": "python_version >= '3.9'",
"version": "==8.18.1"
},
"isbnlib": {
@@ -1412,7 +1411,6 @@
"sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"
],
"index": "pypi",
- "markers": "python_version >= '3.7'",
"version": "==4.17.3"
},
"jupyter-client": {
@@ -2333,7 +2331,7 @@
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"version": "==2.8.2"
},
"python-geoip": {
@@ -2771,7 +2769,7 @@
"sha256:febffa5b1eda6622d44b245b0685aff6fb555ce0ed734e2d7b1c3acd018a2cff",
"sha256:ff836cd4041e16003549449cc0a5e372f6b6f871eb89007ab0ee18fb2800fded"
],
- "markers": "python_version >= '2.5' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "markers": "python_version >= '2.5' and python_version not in '3.0, 3.1, 3.2'",
"version": "==3.19.2"
},
"simplekv": {
@@ -2787,7 +2785,7 @@
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"version": "==1.16.0"
},
"snowballstemmer": {
@@ -3148,7 +3146,7 @@
"sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d",
"sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"
],
- "markers": "python_version >= '3.6'",
+ "markers": "python_version >= '3.8'",
"version": "==2.2.1"
},
"uwsgi": {
@@ -3287,20 +3285,12 @@
],
"version": "==0.12.0"
},
- "zammad-py": {
- "hashes": [
- "sha256:59af11dec42b4edf16ea4d538cc3fa07bc47cf7efebdca214d21c6c5bf4a6e2f",
- "sha256:acd770cf98721536ccc88de9461cf8be04e1c30f8def400ae8825f0e8ebe6a72"
- ],
- "markers": "python_version < '4.0' and python_full_version >= '3.8.1'",
- "version": "==3.0.0"
- },
"zenodo-legacy": {
- "editable": "True",
+ "editable": true,
"path": "./legacy"
},
"zenodo-rdm": {
- "editable": "True",
+ "editable": true,
"path": "./site"
},
"zipp": {
@@ -3629,7 +3619,6 @@
"sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"
],
"index": "pypi",
- "markers": "python_full_version >= '3.8.0'",
"version": "==5.13.2"
},
"itsdangerous": {
@@ -3807,7 +3796,6 @@
"sha256:1d339b004f764d6cd0f06e690f6dd748df3d62e6fe1a692d6a5500ac2c5b75a5"
],
"index": "pypi",
- "markers": "python_version >= '2.7'",
"version": "==0.3.12"
},
"pytest-cov": {
@@ -3832,7 +3820,6 @@
"sha256:eda7d69c20d6b7d58f0415e80473ec3f7d1604f00368f46437290051dfdbe216"
],
"index": "pypi",
- "markers": "python_version >= '3.7'",
"version": "==2.1.7"
},
"pytest-isort": {
@@ -3948,7 +3935,7 @@
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
- "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
"version": "==0.10.2"
},
"tomli": {
@@ -3972,7 +3959,7 @@
"sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d",
"sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"
],
- "markers": "python_version >= '3.6'",
+ "markers": "python_version >= '3.8'",
"version": "==2.2.1"
},
"werkzeug": {
diff --git a/site/setup.cfg b/site/setup.cfg
index 3a3c02aa..ad7eb426 100644
--- a/site/setup.cfg
+++ b/site/setup.cfg
@@ -23,7 +23,6 @@ install_requires =
nameparser>=1.1.1
pyinstrument>=4.5.1
sqltap>=0.3.11
- zammad-py>=3.0.0,<4.0.0
# TODO: Add once we fix PyPI package issues
# invenio-swh>=0.2.0,<1.0.0
diff --git a/site/tests/conftest.py b/site/tests/conftest.py
index 674c8327..ff0285ab 100644
--- a/site/tests/conftest.py
+++ b/site/tests/conftest.py
@@ -79,7 +79,6 @@ def app_config(app_config):
}
# OpenAIRE configs
app_config["OPENAIRE_PORTAL_URL"] = "https://explore.openaire.eu"
- app_config["SUPPORT_ZAMMAD_HTTPTOKEN"] = "changeme"
return app_config
diff --git a/site/zenodo_rdm/config.py b/site/zenodo_rdm/config.py
index 021a3826..3038c5a7 100644
--- a/site/zenodo_rdm/config.py
+++ b/site/zenodo_rdm/config.py
@@ -31,116 +31,132 @@
# I18N_TRANSLATIONS_PATHS = [os.path.abspath("./site/zenodo_rdm/translations")]
-# API endpoint to Zammad instance
-SUPPORT_ZAMMAD_ENDPOINT = "http://localhost:8080/api/v1"
+# Email address of sender.
+SUPPORT_SENDER_EMAIL = "info@zenodo.org"
-# Zammad token
-SUPPORT_ZAMMAD_HTTPTOKEN = ""
+# Name of the sender
+SUPPORT_SENDER_NAME = "Zenodo Support"
+
+# Support emails
+SUPPORT_EMAILS = ["info@zenodo.org"]
+
+# Support signature
+SUPPORT_SIGNATURE = "https://zenodo-rdm.web.cern.ch/"
# Support form categories
SUPPORT_ISSUE_CATEGORIES = [
- {
- "key": "general-inquiry",
- "title": "General inquiry",
- "description": "",
- },
- {
- "key": "problem-report",
- "title": "Bug or problem",
- "description": "Please provide direct links to pages and screenshots if possible. Include the error identifier if shown.",
- },
{
"key": "file-modification",
"title": "File modification",
"description": (
- "File modifications are possible within a short grace period after publishing:"
- "
"
- "- Published <=30 days (accepted): File modifications are accepted within a 30 days grace period after publishing your record.
"
- '- Published >30 days (declined): Please use our versioning feature for records published >30 days ago. File modification requests made after the 30-day grace period are declined.
'
- "
"
- "Please provide the following information:"
- ""
- "- Justification: Short justification for the file change.
"
- "- Record URL: Please provide a direct link to the record you would like to modify (e.g. https://zenodo.org/records/1234).
"
- "- Actions: Please specify all changes you would like to perform (add/replace/delete/rename). Please specify the exact filename for each action.
"
- "- Files: Please provide the files on a publicly-accessible URL(s) or for smaller files attach them on the form.
"
- "
"
+ "All requests related to updating files in already published "
+ "record(s). This includes new file addition, file removal or "
+ "file replacement. "
+ "Before sending a request, please consider creating a "
+ 'new version '
+ "of your upload. Please first consult our "
+ 'FAQ to get familiar'
+ " with the file update conditions, to see if your case is "
+ "eligible.
"
+ "You request has to contain all of the points below:"
+ ""
+ "- Provide a justification for the file change in the "
+ "description.
"
+ "- Mention any use of the record(s) DOI in publications or "
+ "online, e.g.: list papers that cite your record and "
+ "provide links to posts on blogs and social media. "
+ "Otherwise, state that to the best of your knowledge the DOI has "
+ "not been used anywhere.
"
+ "- Specify the record(s) you want to update by the Zenodo"
+ ' URL, e.g.: "https://zenodo.org/record/8428".
'
+ "Providing only the record's title, publication date or a "
+ "screenshot with search result is not explicit enough. "
+ "- If you want to delete or update a file, specify it "
+ "by its filename, and mention if you want the name to "
+ "remain as is or changed (by default the filename of the new "
+ "file will be used).
"
+ "- Upload the new files below or provide a publicly-accessible "
+ "URL(s) with the files in the description.
"
+ "
"
+ "Not providing full information on any of the points above "
+ "will significantly slow down your request resolution, "
+ "since our support staff will have to reply back with a request "
+ "for missing information."
),
},
{
- "key": "quota-increase",
- "title": "Quota increase",
+ "key": "upload-quota",
+ "title": "File upload quota increase",
"description": (
- "We exceptionally grant a one-time quota increase up to 200GB. Requests beyond the 200GB are declined. Zenodo allows maximum 100 files in a record (this limit cannot be increased).
"
- 'Before you send the request, please follow the actions described on https://help.zenodo.org/docs/deposit/manage-files/quota-increase/.
'
+ "All requests for a quota increase beyond the 50GB limit. "
+ "Please include the following information with your request:"
+ ""
+ "- The total size of your dataset, number of files and the "
+ "largest file in the dataset. When referring to file sizes"
+ ' use '
+ "SI units
"
+ "- Information related to the organization, project or grant "
+ "which was involved in the research, which produced the "
+ "dataset.
"
+ "- Information on the currently in-review or future papers that "
+ "will cite this dataset (if applicable). If possible specify the "
+ "journal or conference.
"
+ "
"
),
},
{
- "key": "record-deletion",
- "title": "Record deletion",
+ "key": "record-inactivation",
+ "title": "Record inactivation",
"description": (
- "The request must be made by the uploader (if you're not the uploader, choose take-down notice instead).
"
- "Record deletion is possible within a short grace period after publishing:
"
- ""
- "- Published <=30 days (accepted): Record deletions are accepted within a 30 days grace period after publishing your record.
"
- '- Published >30 days (declined): Please use our versioning feature for records published >30 days ago. File modification requests made after the 30-day grace period are declined.
'
- "
"
- "Please provide:
"
- ""
- "- Record URL: A direct link to the record you would like to delete (e.g. https://zenodo.org/records/1234). If multiple versions should be deleted, please include one link per version.
"
- "
"
+ "Requests related to record inactivation, either by the record "
+ "owner or a third party. Please specify the record(s) in question "
+ "by the URL(s), and reason for the inactivation."
),
},
{
- "key": "user-deletion",
- "title": "User deletion",
+ "key": "openaire",
+ "title": "OpenAIRE",
"description": (
- "Please make sure you log in before you send the request. If you have uploaded any records or created any communities, please specify to who these should be transferred."
+ "All questions related to OpenAIRE reporting and grants. "
+ "Before sending a request, make sure your problem was not "
+ "already resolved, see OpenAIRE "
+ 'FAQ. '
+ "For questions unrelated to Zenodo, you should contact OpenAIRE "
+ ''
+ "helpdesk directly."
),
},
{
- "key": "ownership-transfer",
- "title": "Ownership transfer",
- "description": "",
- },
- {
- "key": "personal-data-report",
- "title": "Report personal data exposure",
+ "key": "partnership",
+ "title": "Partnership, outreach and media",
"description": (
- "Please provide the following information:
"
- ""
- "- Link: A direct link to the record containing personal data.
"
- "- Reason: A description of where the personal data appears.
"
- "
"
+ "All questions related to possible partnerships, outreach, "
+ "invited talks and other official inquiries by media."
+ "If you are a journal, organization or conference organizer "
+ "interested in using Zenodo as archive for your papers, software "
+ "or data, please provide details for your usecase."
),
},
{
- "key": "security-report",
- "title": "Report a security issue",
- "description": "Please provide as detailed information as possible.",
- },
- {
- "key": "spam-report",
- "title": "Report spam",
+ "key": "tech-support",
+ "title": "Security issue, bug or spam report",
"description": (
- "Please provide the following information:
"
- ""
- "- Link: A direct link to the spam record/community.
"
- "- Reason: A short description of why the record is spam.
"
- "
"
+ "Report a technical issue or a spam content on Zenodo."
+ "Please provide details on how to reproduce the bug. "
+ "Upload any screenshots or files which are relevant to the issue "
+ "or to means of reproducing it. Include error messages and "
+ "error codes you might be getting in the description.
"
+ "For REST API errors, provide a minimal code which produces the "
+ "issues. Use external services for scripts and long text"
+ ', e.g.: GitHub Gist. '
+ "Do not disclose your password or REST API access tokens."
+ ""
),
},
{
- "key": "feature-request",
- "title": "Feedback/Feature request",
- "description": "",
- },
- {
- "key": "take-down",
- "title": "Take-down notice",
- "description": (
- "Please provide a direct link to the record or community to request us to take down. Please specify the reason for the take-down (e.g. copyright infringement, plagiarism, fraud, or similar)."
- ),
+ "key": "other",
+ "title": "Other",
+ "description": ("Questions which do not fit into any other category."),
},
]
@@ -156,6 +172,18 @@
# Support url endpoint
SUPPORT_ENDPOINT = "/support"
+#: Email body template.
+SUPPORT_EMAIL_BODY_TEMPLATE = "zenodo_rdm/email_body.html"
+
+#: Email title template.
+SUPPORT_EMAIL_TITLE_TEMPLATE = "zenodo_rdm/email_title.html"
+
+#: Email body template.
+SUPPORT_EMAIL_CONFIRM_BODY_TEMPLATE = "zenodo_rdm/email_confirm_body.html"
+
+#: Email title template.
+SUPPORT_EMAIL_CONFIRM_TITLE_TEMPLATE = "zenodo_rdm/email_confirm_title.html"
+
# Search query of recent uploads
# Defaults to newest records search
ZENODO_FRONTPAGE_RECENT_UPLOADS_QUERY = "type:(dataset OR software OR poster OR presentation) AND _exists_:parent.communities AND access.files:public"
diff --git a/site/zenodo_rdm/support/email.py b/site/zenodo_rdm/support/email.py
new file mode 100644
index 00000000..ca7484b7
--- /dev/null
+++ b/site/zenodo_rdm/support/email.py
@@ -0,0 +1,126 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2023 CERN.
+#
+# ZenodoRDM is free software; you can redistribute it and/or modify it
+# under the terms of the MIT License; see LICENSE file for more details.
+"""Implements an email service dedicated to the support views of ZenodoRDM."""
+
+from flask import current_app, request
+from flask_login import current_user
+from flask_mail import Message
+from invenio_accounts.sessions import _extract_info_from_useragent
+
+from .utils import format_user_email, render_template_to_string
+
+
+class EmailService(object):
+ """Basic service to send an e-mail using the configured mail extension."""
+
+ @property
+ def mail(self):
+ """Retrieves the current mail extension.
+
+ Throws an exception if none is found.
+ """
+ service = current_app.extensions["mail"]
+ if not service:
+ raise Exception("No mail extension provided.")
+ return service
+
+ def send_email(
+ self, title, recipients, sender, body, reply_to="", attachments=None
+ ):
+ """Sends an e-mail to the given recipient(s)."""
+ if type(recipients) != list:
+ recipients = [recipients]
+
+ msg = Message(
+ title, sender=sender, recipients=recipients, reply_to=reply_to, body=body
+ )
+ if attachments:
+ for upload in attachments:
+ msg.attach(upload.filename, "application/octet-stream", upload.read())
+ self.mail.send(msg)
+
+
+class SupportEmailService(EmailService):
+ """Provides an interface to send support related emails."""
+
+ def __init__(self):
+ """Constructor."""
+ super().__init__()
+
+ def send_support_email(
+ self,
+ sender_name,
+ email,
+ description,
+ send_user_agent,
+ subject,
+ category,
+ files,
+ title,
+ ):
+ """Sends an email to the configured support."""
+ user_agent = _extract_info_from_useragent(request.headers.get("User-Agent"))
+ context = {
+ "user_id": current_user.get_id(),
+ "name": sender_name,
+ "email": email,
+ "description": description,
+ "user_agent": user_agent if send_user_agent else None,
+ "subject": subject,
+ "category": category,
+ }
+ msg_body = render_template_to_string(self.support_email_body_template, context)
+ msg_title = title
+ sender = format_user_email(email, sender_name)
+ recipients = self.support_emails
+ reply_to = email
+ return self.send_email(msg_title, recipients, sender, msg_body, reply_to, files)
+
+ # TODO aggregate in a single method and just pass things
+ def send_confirmation_email(self, recipients):
+ """Sends a confirmation e-mail to the user."""
+ context = {
+ "support_name": self.support_sender_name,
+ "support_signature": current_app.config["SUPPORT_SIGNATURE"],
+ }
+ sender = format_user_email(self.support_sender_email, self.support_sender_name)
+ msg_body = render_template_to_string(
+ self.support_email_confirm_body_template, context
+ )
+ msg_title = self.support_sender_name
+ reply_to = self.support_sender_email
+ return self.send_email(msg_title, recipients, sender, msg_body, reply_to)
+
+ @property
+ def support_sender_email(self):
+ """Support sender e-mail."""
+ return current_app.config["SUPPORT_SENDER_EMAIL"]
+
+ @property
+ def support_sender_name(self):
+ """Support sender name."""
+ return current_app.config["SUPPORT_SENDER_NAME"]
+
+ @property
+ def support_email_body_template(self):
+ """Support email body template."""
+ return current_app.config["SUPPORT_EMAIL_BODY_TEMPLATE"]
+
+ @property
+ def support_email_confirm_title_template(self):
+ """Support confirmation email title."""
+ return current_app.config["SUPPORT_EMAIL_CONFIRM_TITLE_TEMPLATE"]
+
+ @property
+ def support_email_confirm_body_template(self):
+ """Support confirmation email body."""
+ return current_app.config["SUPPORT_EMAIL_CONFIRM_BODY_TEMPLATE"]
+
+ @property
+ def support_emails(self):
+ """List of support emails."""
+ return current_app.config["SUPPORT_EMAILS"]
diff --git a/site/zenodo_rdm/support/errors.py b/site/zenodo_rdm/support/errors.py
new file mode 100644
index 00000000..954c5f58
--- /dev/null
+++ b/site/zenodo_rdm/support/errors.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2023 CERN.
+#
+# ZenodoRDM is free software; you can redistribute it and/or modify it
+# under the terms of the MIT License; see LICENSE file for more details.
+"""ZenodoRDM support error classes."""
+
+from ..errors import ZenodoRDMError
+
+
+class ConfirmationEmailNotSent(ZenodoRDMError):
+ """Implements an error when a support confirmation e-mail was not sent."""
+
+ def __init__(self, message):
+ """Initialise error."""
+ super().__init__("Confirmation email failed to send. Error: {}".format(message))
+
+
+class SupportEmailNotSent(ZenodoRDMError):
+ """Implements an error when a support e-mail was not sent."""
+
+ def __init__(self, message):
+ """Initialise error."""
+ super().__init__("Support email failed to send. Error: {}".format(message))
diff --git a/site/zenodo_rdm/support/schema.py b/site/zenodo_rdm/support/schema.py
index 0bf9e54f..4c26a132 100644
--- a/site/zenodo_rdm/support/schema.py
+++ b/site/zenodo_rdm/support/schema.py
@@ -8,7 +8,7 @@
from flask import current_app
from marshmallow import RAISE, Schema, ValidationError, fields, validate, validates
-from marshmallow_utils.fields import SanitizedUnicode
+from marshmallow_utils.fields import SanitizedHTML
from werkzeug.local import LocalProxy
from werkzeug.utils import cached_property
@@ -33,7 +33,7 @@ class Meta:
name = fields.String(required=True)
email = fields.String(required=True, validate=validate.Email())
- description = SanitizedUnicode(required=True)
+ description = SanitizedHTML(required=True)
subject = fields.String(required=True)
category = fields.String(required=True)
sysInfo = fields.Boolean()
diff --git a/site/zenodo_rdm/support/support.py b/site/zenodo_rdm/support/support.py
index 3a92226c..b01f7f47 100644
--- a/site/zenodo_rdm/support/support.py
+++ b/site/zenodo_rdm/support/support.py
@@ -4,22 +4,18 @@
#
# ZenodoRDM is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
-
"""Implements the support view for ZenodoRDM."""
-import mimetypes
-from base64 import b64encode
+import smtplib
from collections import OrderedDict
-from flask import current_app, flash, redirect, render_template, request, url_for
+from flask import current_app, redirect, render_template, request, url_for
from flask.views import MethodView
-from flask_login import current_user
from invenio_accounts.sessions import _extract_info_from_useragent
-from invenio_i18n import _
-from requests.exceptions import RequestException
from werkzeug.utils import cached_property
-from zammad_py import ZammadAPI
+from .email import SupportEmailService
+from .errors import ConfirmationEmailNotSent, SupportEmailNotSent
from .schema import SupportFormSchema
@@ -29,11 +25,8 @@ class ZenodoSupport(MethodView):
def __init__(self):
"""Constructor."""
self.template = "zenodo_rdm/support.html"
+ self.email_service = SupportEmailService()
self.support_form_schema = SupportFormSchema()
- self.client = ZammadAPI(
- url=current_app.config["SUPPORT_ZAMMAD_ENDPOINT"],
- http_token=current_app.config["SUPPORT_ZAMMAD_HTTPTOKEN"],
- )
def get(self):
"""Renders the support template."""
@@ -43,6 +36,7 @@ def get(self):
browser_string = browser_client + " " + browser_version
platform = user_agent.get("os", "")
system_info = {"browser": browser_string, "platform": platform}
+
return render_template(
self.template,
categories=self.categories,
@@ -53,137 +47,65 @@ def post(self):
"""Receives a form, validates its data and handles it."""
input_data = {**request.form.to_dict(), "files": request.files.getlist("files")}
data = self.validate_form(input_data)
- try:
- self.handle_form(data)
- except RequestException as e:
- # Any error from Zammad being down to badly formatted requests.
- raise Exception(
- "The request could not be sent to the support system due to an internal error."
- )
+ self.handle_form(data)
+
return redirect(url_for("invenio_app_rdm.frontpage_view_function"))
+ def handle_form(self, form_data):
+ """Form controller."""
+ self.send_support_email(form_data)
+ self.send_confirmation_email(form_data)
+
def validate_form(self, form_data):
"""Validates form using a schema."""
return self.support_form_schema.load(form_data)
- def handle_form(self, data):
- """Form controller."""
- customer_id = self.handle_customer(data["email"], data["name"])
-
- params = {
- "title": data["subject"],
- "group": "Support (1st line)",
- "customer_id": customer_id,
- "sender": "Customer",
- "type": data["category"],
- "article": {
- "subject": data["subject"],
- "body": data["description"],
- "content_type": "text/plain",
- "type": "web",
- "internal": False,
- "sender": "Customer",
- "origin_by_id": customer_id,
- },
- }
- if "files" in data:
- params["article"]["attachments"] = []
- # Limit to max 20 files
- for f in data["files"][:20]:
- params["article"]["attachments"].append(
- {
- "filename": f.filename,
- "data": b64encode(f.stream.read()),
- "mime-type": mimetypes.guess_type(f.filename)[0]
- or "application/octet-stream",
- }
+ def send_support_email(self, data):
+ """Send an email to support."""
+ try:
+ self.email_service.send_support_email(
+ sender_name=data.get("name"),
+ email=data.get("email"),
+ description=data.get("description"),
+ send_user_agent=data.get("sysInfo"),
+ subject=data.get("subject"),
+ category=data.get("category"),
+ files=data.get("files"),
+ title="[{}]: {}".format(data.get("category"), data.get("subject")),
+ )
+ except smtplib.SMTPSenderRefused as e:
+ raise SupportEmailNotSent(
+ "There was an issue sending an email to the provided "
+ "address (ours), please make sure it is correct. "
+ "If this issue persists you can send "
+ "us an email directly to {}".format(self.email_service.support_emails)
+ )
+ except Exception as e:
+ raise SupportEmailNotSent(
+ "There was an issue sending the support request."
+ "If this issue persists send us an email directly to {}".format(
+ self.email_service.support_emails
)
-
- # Browser info is added as a note.
- if data["sysInfo"]:
- user_agent = _extract_info_from_useragent(request.headers.get("User-Agent"))
- browser_client = user_agent.get("browser", "")
- browser_version = user_agent.get("browser_version", "")
- browser_string = browser_client + " " + browser_version
- platform = user_agent.get("os", "")
- params["article"]["body"] += f"\n\nBrowser: {browser_string} OS: {platform}"
-
- new_ticket = self.client.ticket.create(params=params)
- flash(
- _(
- "You support request #%(ticket_id)s was created. You will receive a confirmation email shortly."
)
- % {"ticket_id": new_ticket["id"]},
- "info",
- )
- def find_customer(self, email):
- """Find a customer."""
- customer = None
- page = self.client.user.search(f"email:{email}")
- for u in page:
- if u["email"] == email:
- customer = u
- break
- return customer
-
- def split_name(self, name):
- """Split name into given and family name."""
- parts = name.strip().split(" ")
- if len(parts) == 1:
- return name.strip(), ""
- elif len(parts) > 1:
- return parts[0], " ".join(parts[1:]).strip()
- else:
- return "", ""
-
- def create_customer(self, email, name, zenodo_user_id):
- """Create a customer."""
- params = {
- "email": email,
- "roles": ["Customer"],
- }
- if name:
- first_name, last_name = self.split_name(name)
- params["firstname"] = first_name
- params["lastname"] = last_name
- if zenodo_user_id is not None:
- params["zenodo_user"] = zenodo_user_id
- return self.client.user.create(params=params)
-
- def update_customer(self, customer, email, name, zenodo_user_id):
- """Update a customer."""
- params = {}
- if zenodo_user_id:
- if customer.get("zenodo_user", None) != zenodo_user_id:
- params["zenodo_user"] = zenodo_user_id
- if name:
- firstname, lastname = self.split_name(name)
- if customer["firstname"] != firstname:
- params["firstname"] = firstname
- if customer["lastname"] != lastname:
- params["lastname"] = lastname
- if params:
- customer = self.client.user.update(customer["id"], params=params)
- return customer
-
- def handle_customer(self, email, name):
- """Create the customer in Zammad."""
- # Set initial parameters
- zenodo_user_id = None
- if current_user.is_authenticated:
- email = current_user.email
- name = current_user.user_profile.get("full_name", "")
- zenodo_user_id = current_user.get_id()
- email = email.lower()
- # TODO: Create or update organisation if not already in Zammad.
- customer = self.find_customer(email)
- if customer is None:
- customer = self.create_customer(email, name, zenodo_user_id)
- else:
- self.update_customer(customer, email, name, zenodo_user_id)
-
- return customer["id"]
+ def send_confirmation_email(self, data):
+ """Send a confirmation email to the user."""
+ try:
+ self.email_service.send_confirmation_email(recipients=data.get("email"))
+ except smtplib.SMTPSenderRefused as e:
+ raise ConfirmationEmailNotSent(
+ "There was an issue sending a confirmation email to the provided "
+ "address (yours), please make sure it is correct. "
+ "If this issue persists you can send "
+ "us an email directly to {}".format(self.email_service.support_emails)
+ )
+ except Exception as e:
+ raise ConfirmationEmailNotSent(
+ "There was an issue sending the confirmation email."
+ "If this issue persists send us an email directly to {}".format(
+ self.email_service.support_emails
+ )
+ )
@cached_property
def categories(self):
@@ -191,3 +113,8 @@ def categories(self):
return OrderedDict(
(c["key"], c) for c in current_app.config["SUPPORT_ISSUE_CATEGORIES"]
)
+
+ @cached_property
+ def support_emails(self):
+ """List of support emails."""
+ return current_app.config["SUPPORT_EMAILS"]
diff --git a/site/zenodo_rdm/support/utils.py b/site/zenodo_rdm/support/utils.py
new file mode 100644
index 00000000..f3d11b27
--- /dev/null
+++ b/site/zenodo_rdm/support/utils.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2023 CERN.
+#
+# ZenodoRDM is free software; you can redistribute it and/or modify it
+# under the terms of the MIT License; see LICENSE file for more details.
+"""Utility functions for the ZenodoRDM support views."""
+
+from flask import current_app
+
+
+def render_template_to_string(template, context):
+ """Render a Jinja template with the given context."""
+ template = current_app.jinja_env.get_or_select_template(template)
+ return template.render(context)
+
+
+def format_user_email(email, name):
+ """Format the user's email as 'Full Name ' or 'email'."""
+ if name:
+ email = "{name} <{email}>".format(name=name, email=email)
+ return email
diff --git a/site/zenodo_rdm/templates/semantic-ui/zenodo_rdm/email_body.html b/site/zenodo_rdm/templates/semantic-ui/zenodo_rdm/email_body.html
new file mode 100644
index 00000000..9dbb8b32
--- /dev/null
+++ b/site/zenodo_rdm/templates/semantic-ui/zenodo_rdm/email_body.html
@@ -0,0 +1,35 @@
+{#
+# This file is part of Zenodo.
+# Copyright (C) 2022 CERN.
+#
+# Zenodo is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# Zenodo is distributed in the hope that it will be
+# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Zenodo; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+# MA 02111-1307, USA.
+#
+# In applying this license, CERN does not
+# waive the privileges and immunities granted to it by virtue of its status
+# as an Intergovernmental Organization or submit itself to any jurisdiction.
+-#}
+
+From: {{ name }} <{{ email }}> {% if user_id %}({{user_id}}){% endif %}
+
+{%- if user_agent %}
+Operating System: {{ user_agent.os }}
+Browser: {{ user_agent.browser }}
+Device: {{ user_agent.device }}
+{%- else %}
+User's browser information not included.
+{%- endif %}
+
+{{ description | safe }}
diff --git a/site/zenodo_rdm/templates/semantic-ui/zenodo_rdm/email_confirm_body.html b/site/zenodo_rdm/templates/semantic-ui/zenodo_rdm/email_confirm_body.html
new file mode 100644
index 00000000..e5d1660c
--- /dev/null
+++ b/site/zenodo_rdm/templates/semantic-ui/zenodo_rdm/email_confirm_body.html
@@ -0,0 +1,32 @@
+{#
+# This file is part of Zenodo.
+# Copyright (C) 2022 CERN.
+#
+# Zenodo is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# Zenodo is distributed in the hope that it will be
+# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Zenodo; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+# MA 02111-1307, USA.
+#
+# In applying this license, CERN does not
+# waive the privileges and immunities granted to it by virtue of its status
+# as an Intergovernmental Organization or submit itself to any jurisdiction.
+-#}
+
+Thank you for contacting {{ support_name }}.
+
+We have received your message, and we will do our best to get back to you as soon as possible.
+This is an automated confirmation of your request, please do not reply to this email.
+
+{{ support_name }}
+{{ support_signature }}
+
diff --git a/site/zenodo_rdm/templates/semantic-ui/zenodo_rdm/support.html b/site/zenodo_rdm/templates/semantic-ui/zenodo_rdm/support.html
index a600d125..7a0799af 100644
--- a/site/zenodo_rdm/templates/semantic-ui/zenodo_rdm/support.html
+++ b/site/zenodo_rdm/templates/semantic-ui/zenodo_rdm/support.html
@@ -22,93 +22,16 @@
-
-
-
-
-
-
-
We appreciate that you search for answers to your questions in below linked documentation before you reach out to us.
-
-
-
-
-
-
-
-
- Answers to all of the most
frequently asked questions on our support line including e.g.
-
- - Account management
- - Creating and managing records
- - Review processes
- - Safe listing and usage statistics
- - Policy questions
-
-
-
-
-
-
-
-
- Easy to follow
step-by-step instructions for how to get started and use both easy and advanced Zenodo features including:
-
- - Uploading and managing records
- - Collaborating and sharing
- - Communities and membership management
- - Account and profiles
-
-
-
-
-
-
-
-
- General information about Zenodo, our infrastructure, projects, and policies including:
-
-
-
-
-
-
-
-
- OpenAIRE provides general guides/training on open science, Horizon Europe, research data management (RDM) and legal issues for researchers.
-
-
-
-
-
-
-
+
+
Before making a request, you can have a look at our FAQ and other resources for more detailed information about the operation, features and goals of Zenodo.
+ {%- if not user %}
+
It is recommended to login before sending a request, so we can automatically fill-in your contact information. It is especially important for quicker resolution of all technical issues and requests concerning your Zenodo account.
+ {%- endif %}
-
Contact us
- {%- if not is_user_authenticated %}
-
-
-
Log in
-
Please log into Zenodo if you have already have an account by clicking the button. This helps us resolve your requests faster and is required for certain types of requests.
-
- {%- endif %}
{% block root_section %}
{% endblock %}
@@ -137,4 +60,4 @@
Contact us
{% block javascript %}
{{ super() }}
{{ webpack['zenodo-rdm-support.js'] }}
-{% endblock %}
+{% endblock %}
\ No newline at end of file