Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add default email template #1898

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 32 additions & 9 deletions esp/esp/program/modules/handlers/commmodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
from django.template import Template
from django.template import Context as DjangoContext
from esp.middleware import ESPError
from django.template import loader

import re

Expand All @@ -71,10 +72,12 @@ def commprev(self, request, tl, one, two, module, extra, prog):
from esp.users.models import PersistentQueryFilter
from django.conf import settings

filterid, listcount, subject, body = [request.POST['filterid'],
request.POST['listcount'],
request.POST['subject'],
request.POST['body'] ]
filterid, listcount, subject, msglang, body = [
request.POST['filterid'],
request.POST['listcount'],
request.POST['subject'],
request.POST['msglang'],
request.POST['body'] ]
sendto_fn_name = request.POST.get('sendto_fn_name', MessageRequest.SEND_TO_SELF_REAL)
selected = request.POST.get('selected')
public_view = 'public_view' in request.POST
Expand Down Expand Up @@ -118,6 +121,14 @@ def commprev(self, request, tl, one, two, module, extra, prog):

MessageRequest.assert_is_valid_sendto_fn_or_ESPError(sendto_fn_name)

# If they were trying to use plain text, sanitize the content and
# make whitespace WYSIWYG from textbox before rendering HTML.
if msglang == 'plaintext':
htmlbody = body.replace('&', '&amp;').replace('<', '&lt;'
).replace('>', '&gt;').replace('\n', '<br />').replace(
' ', '&nbsp;&nbsp;')
else:
htmlbody = body
# If they used the rich-text editor, we'll need to add <html> tags
if '<html>' not in body:
body = '<html>' + body + '</html>'
Expand All @@ -126,7 +137,11 @@ def commprev(self, request, tl, one, two, module, extra, prog):
'program': ActionHandler(self.program, firstuser),
'request': ActionHandler(MessageRequest(), firstuser)}

renderedtext = Template(body).render(DjangoContext(contextdict))
htmlbody = unicode(loader.get_template('email/default_email.html'
).render(DjangoContext({'msgbdy': htmlbody,
'user': ActionHandler(firstuser, firstuser),
'program': ActionHandler(self.program, firstuser)})))
renderedtext = Template(htmlbody).render(DjangoContext(contextdict))

return render_to_response(self.baseDir()+'preview.html', request,
{'filterid': filterid,
Expand All @@ -136,8 +151,10 @@ def commprev(self, request, tl, one, two, module, extra, prog):
'subject': subject,
'from': fromemail,
'replyto': replytoemail,
'msglang': msglang,
'public_view': public_view,
'body': body,
'htmlbody': htmlbody,
'renderedtext': renderedtext})

@staticmethod
Expand Down Expand Up @@ -170,12 +187,13 @@ def commfinal(self, request, tl, one, two, module, extra, prog):
from esp.dbmail.models import MessageRequest
from esp.users.models import PersistentQueryFilter

filterid, fromemail, replytoemail, subject, body = [
filterid, fromemail, replytoemail, subject, body, htmlbody = [
request.POST['filterid'],
request.POST['from'],
request.POST['replyto'],
request.POST['subject'],
request.POST['body'] ]
request.POST['body'],
request.POST['htmlbody'] ]
sendto_fn_name = request.POST.get('sendto_fn_name', MessageRequest.SEND_TO_SELF_REAL)
public_view = 'public_view' in request.POST

Expand All @@ -198,7 +216,10 @@ def commfinal(self, request, tl, one, two, module, extra, prog):
sendto_fn_name = sendto_fn_name,
sender = fromemail,
creator = request.user,
msgtext = body,
msgtext = unicode(loader.get_template('email/default_email.html').render(DjangoContext(
{'msgbdy': htmlbody,
'user': request.user,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This causes the template to render user variables in the template itself (not message body) with information about the sender. Not a problem with the default since there's no use of the user, but this is what's causing Yale's bug. Since the template is going to get rendered into one copy for all users, which then gets further transformed later into the individual copies, user information shouldn't be passed here at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to add user information into the template? It would be very useful to put, for instance, the username the email was sent to.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you can still put user information in the template; it will just get interpreted on the second render pass, when we add user variables.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't quite understand what the difference is / what needs to be done in order to make sure the right thing happens instead of the variables being rendered in the template?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This template should get rendered twice. First, here, we render msgbdy into default_email_html.txt to get msgtext. This happens once, and therefore should not include any user data, because we don't yet know what user the email will be sent to. Then, later, below, we the output of that (msgtext) into the actual email, once for each user, injecting their information into the template. Any variables in msgtext will get interpreted on the second pass. When you include request.user the first time, it means that any user variables in default_email_html.txt get rendered according to the user sending the email, because that's who request.user is; instead you want to render them the second time with the email's recipient as the user.

I wrote too quickly above and was a bit incorrect: any template variables that you put in default_email_html.txt that should get rendered on the second pass, i.e., any user variables, need to have their template tags excaped, using {% templatetag openvariable %} instead of {{ and {% templatetag closevariable %} instead of }}. This way, the first rendering pass will convert those to {{ and }} and then the second rendering pass will see them as normal variables, and interpolate the correct user's information.

'program': self.program }))),
public = public_view,
special_headers_dict
= { 'Reply-To': replytoemail, }, )
Expand Down Expand Up @@ -306,12 +327,13 @@ def commpanel(self, request, tl, one, two, module, extra, prog):
@needs_admin
def maincomm2(self, request, tl, one, two, module, extra, prog):

filterid, listcount, fromemail, replytoemail, subject, body = [
filterid, listcount, fromemail, replytoemail, subject, msglang, body = [
request.POST['filterid'],
request.POST['listcount'],
request.POST['from'],
request.POST['replyto'],
request.POST['subject'],
request.POST['msglang'],
request.POST['body'] ]
sendto_fn_name = request.POST.get('sendto_fn_name', MessageRequest.SEND_TO_SELF_REAL)
selected = request.POST.get('selected')
Expand All @@ -325,6 +347,7 @@ def maincomm2(self, request, tl, one, two, module, extra, prog):
'from': fromemail,
'replyto': replytoemail,
'subject': subject,
'msglang': msglang,
'body': body,
'public_view': public_view})

Expand Down
17 changes: 17 additions & 0 deletions esp/templates/email/default_email.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<html>
{% autoescape off %}
{{ msgbdy|safe }}
{% endautoescape %}
<small>
You received this email because you have signed up for an account on our website,
<a href=>{% templatetag openvariable %}EMAIL_HOST{% templatetag closevariable %}.
This email was sent to the email account associated with the
user {% templatetag openvariable %}user.username{% templatetag closevariable %}.
If you received the same email message multiple times, you may have duplicate accounts;
please email us to delete them. If you no longer wish for {% templatetag openvariable %}user.username{% templatetag closevariable %}
to receive emails from us, email us or click
<a href="/myesp/disableaccount" style="color: #336699;font-weight: normal;text-decoration: underline;">here</a>.
This action will unsubscribe you from receiving emails to {% templatetag openvariable %}user.username{% templatetag closevariable %}.
You will still receive messages to this email address if it is listed in the contact information for other accounts on our site.
</small>
</html>
38 changes: 38 additions & 0 deletions esp/templates/program/modules/commmodule/commpanel_step2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

function showEmailExplanation() {
document.getElementById("from-help").style.display = '';
}

function hideEmailExplanation() {
document.getElementById("from-help").style.display = 'none';
document.getElementById("from").focus();
}


function validateMsgLang() {
var msgTypes = document.getElementsByName("msglang");
var containsTag = /<(\/?((.)|(br ?\/?)))>|(<img)/i.test(
document.getElementById("emailbody").value);
var containsHTag = /<\/?html>/i.test(
document.getElementById("emailbody").value);

if(containsHTag) {
return confirm("Didn't we say not to include <html> tags?!? "
+ "If you're sure you know what you're doing, "
+ "click 'OK' to continue.");
} else if(msgTypes[1].checked && !containsTag) {
return confirm('You selected "HTML" but included no HTML tags. '
+ 'Continuing might squash your formatting. '
+ 'Would you still like to proceed?');
} else if(msgTypes[0].checked && containsTag) {
return confirm('You selected "Plain Text" but have HTML tags '
+ '(such as <p>) in your message. '
+ 'Continuing will leave your tags in your message '
+ '(so you would see "<b>hello</b>" instead of '
+ 'a bold "hello"). '
+ 'Would you still like to proceed?');
}
else {
return true;
}
}
3 changes: 2 additions & 1 deletion esp/templates/program/modules/commmodule/preview.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ <h2>Preview</h2>
{% endif %}
<input type="hidden" name="listcount" value="{{listcount}}" />
<input type="hidden" name="selected" value="{{selected}}" />
<input type="hidden" name="msglang" value="{{msglang}}" />
<input type="submit" value="Edit Your Email" id="submitform" class="btn btn-default" />
</form>
<form action="/manage/{{program.getUrlBase}}/commfinal" method="post" name="mainback">
Expand All @@ -88,7 +89,7 @@ <h2>Preview</h2>
<input type="hidden" name="from" value="{{from}}" />
<input type="hidden" name="replyto" value="{{replyto}}" />
<input type="hidden" name="subject" value="{{subject}}" />
<input type="hidden" name="body" value="{{body}}" />
<input type="hidden" name="body" value="{{htmlbody}}" />
{% if public_view %}
<input type="hidden" name="public_view" value="on" />
{% endif %}
Expand Down
66 changes: 56 additions & 10 deletions esp/templates/program/modules/commmodule/step2.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,30 @@

{% block xtrajs %}
{{block.super}}
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" href="/resources/demos/style.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>

Check warning

Code scanning / CodeQL

Inclusion of functionality from an untrusted source Medium

Script loaded from content delivery network with no integrity check.
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

Check warning

Code scanning / CodeQL

Inclusion of functionality from an untrusted source Medium

Script loaded from content delivery network with no integrity check.
<script type="text/javascript">
{% include "program/modules/commmodule/commpanel_step2.js" %}
</script>
<script type="text/javascript">
<!-- If coming back from preview, check the language used before -->
window.onload = function() {
if ('{{ msglang }}' != '')
{
document.getElementById("{{msglang}}").checked = "checked";
}
};
</script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jodit/3.2.36/jodit.min.js"></script>
{% endblock %}
{% block stylesheets %}
{{block.super}}
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jodit/3.2.36/jodit.min.css">
<style type="text/css">
.listtable, .listtable th, .listtable td{ border: 1px solid #999; border-collapse: collapse;}
.listtable, .listtable th, .listtable td{ border: 1px solid #999;
border-collapse: collapse;}
#divmaintext .unchosen { background-color: #CCC; }
#divmaintext .chosen { background-color: #FFF; }
#divmaintext td.notchooser { cursor: pointer }
Expand All @@ -30,12 +47,23 @@

<br />
<p>
There are <strong>{{listcount}}</strong> distinct users in your query{% if selected %} of <strong>{{ selected }}</strong>{% endif %}. If you feel this to be off, please
There are <strong>{{listcount}}</strong> distinct users in your
query{% if selected %} of <strong>{{ selected }}</strong>{% endif %}.
If you feel this to be off, please
<a href="javascript:history.go(-1);">go back</a> now.<br />
<br />
Please write your email message now. Note that you can use <tt>{% templatetag openvariable %} parameter {% templatetag closevariable %}</tt> to enter a parameter
into the text of the message. You can use the {% templatetag openvariable %}{% templatetag closevariable %} button to choose from the possible parameters.

Please write your email message now. Note that you can use
<tt>&#123;&#123;parameter&#125;&#125;</tt> to enter a parameter
into the text of the message. You can use the &#123; &#125; button to choose from the possible parameters.
The default
{{ settings.ORGANIZATION_SHORT_NAME }} template (which you can modify
<a href="/admin/utils/templateoverride/?q=default_email.html">here</a>)
is automatically wrapped around your message below.
You can choose to write the body in either plain text or HTML.
If you choose to use HTML, there is no need for <tt>&lt;html&gt;</tt>
and <tt>&lt;/html&gt;</tt> tags because they're are included in the wrapper
template. If you choose plain text, do <b>not</b> use any HTML tags for
whitespace.</p>
<div class="alert alert-danger">
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
Note, the "From" email address must end in <b>@{{ settings.SITE_INFO.1 }}</b> (your website), <b>@learningu.org</b>, or a valid subdomain of learningu.org (i.e., <b>@<i>subdomain</i>.learningu.org</b>).
Expand All @@ -44,18 +72,36 @@
The "Reply-To" field can be any email address (by default it is the same as the "From" email address).
Both email address fields also support named "mailboxes", such as "{{ default_from }}".
</div>

<form action="/manage/{{program.getUrlBase}}/commprev" method="post" name="comm2">
<form action="/manage/{{program.getUrlBase}}/commprev"
onsubmit="return validateMsgLang()" name="comm2" method="post">

<table border="0" cellspacing="0" width="100%">
<tr>
<tr>
<input type="radio" name="msglang" id="plaintext" value="plaintext">Plain Text
<br />
<input type="radio" name="msglang" id="html" value="html" required>HTML

<td>
<label for="from">
<strong>From:</strong>
<small>(If blank: {{ default_from }}</small>)</small>
</label>
<input type="text" size="30" name="from" id="from" value="{{from}}" pattern="(^.+@{{ current_site.domain|regexsite }}$)|(^.+<.+@{{ current_site.domain|regexsite }}>$)|(^.+@(\w+\.)?learningu\.org$)|(^.+<.+@(\w+\.)?learningu\.org>$)"/>
<br />
<input type="text" size="30" name="from" id="from" value="{{from}}"
pattern="(^.+@{{ current_site.domain|regexsite }}$)|(^.+<.+@{{ current_site.domain|regexsite }}>$)|(^.+@(\w+\.)?learningu\.org$)|(^.+<.+@(\w+\.)?learningu\.org>$)" />
<br /><br />
<p class="help-block" id="from-help" style="display:none;"><small>"Your email"
is an alias,
<span onclick="hideEmailExplanation()"
style="float:right;cursor:pointer;">&times;</span>
<script type="text/javascript">
document.write(esp_user.cur_username);</script>@{{ settings.SITE_INFO.1 }},
for your listed email (<script type="text/javascript">
document.write(esp_user.cur_email);</script>),
which will receive all replies from and be visible to all
recipients.</small>
</p>
<br />
<label for="replyto">
<strong>Reply-To:</strong>
<small>(If blank: same as "From")</small>
Expand All @@ -72,7 +118,7 @@
<label for="emailbody">
<strong>Body:</strong>
</label> <br />
<textarea cols="80" rows="20" name="body" id="emailbody">{{body}}</textarea>
<textarea cols="80" rows="20" name="body" id="emailbody" required />{{body}}</textarea>
<br />
</td>
</tr>
Expand Down
Loading