|
1 | | -from app import mail, app, db |
2 | | - |
| 1 | +from app import mail, app |
3 | 2 | from os.path import expanduser, isfile |
4 | | - |
5 | | -from flask.ext.mail import Message |
| 3 | +from flask_mail import Message |
| 4 | +from app.User.coppa import Coppa, SponsorType |
6 | 5 |
|
7 | 6 | import pystache |
8 | 7 | import logging |
9 | 8 |
|
10 | 9 |
|
| 10 | +""" |
| 11 | +TODO: System documentation goes here |
| 12 | +""" |
| 13 | + |
| 14 | + |
11 | 15 | def send_email_template_for_user(id_user, template, server, **kwargs): |
12 | 16 | from app.User.services import get_user |
13 | 17 |
|
14 | | - logging.info("Sending email to user: %s (%s)", id_user, template) |
| 18 | + # Get a copy of the user record |
| 19 | + logging.info("Checking for a valid user record for user ID: %s", id_user) |
| 20 | + user = get_user(id_user) |
| 21 | + |
| 22 | + if user is None: |
| 23 | + logging.error("Cannot send email: Invalid user record") |
| 24 | + return False |
| 25 | + else: |
| 26 | + logging.info("Valid record found for user: %s", user.id) |
| 27 | + |
| 28 | + logging.info("Sending email to user: %s using template: '%s'.", user.email, template) |
15 | 29 |
|
16 | 30 | params = {} |
17 | 31 | for key, value in kwargs.items(): |
| 32 | + logging.debug("Logging parameter %s = %s", key, value) |
18 | 33 | params[key] = value |
19 | 34 |
|
20 | | - user = get_user(id_user) |
21 | | - if user is None: |
22 | | - return False |
23 | | - |
| 35 | + # The elements in the params array represent the data elements that are |
| 36 | + # available to the email templates. |
24 | 37 | params['screenname'] = user.screen_name |
| 38 | + params['email'] = user.email |
| 39 | + params['registrant-email'] = user.email |
| 40 | + params['sponsoremail'] = user.parent_email |
| 41 | + params['blocklyprop-host'] = app.config['CLOUD_SESSION_PROPERTIES']['response.host'] |
| 42 | + |
| 43 | + # Default the recipient email address |
| 44 | + user_email = user.email |
| 45 | + coppa = Coppa() |
| 46 | + |
| 47 | + # Send email to parent if user is under 13 years old |
| 48 | + if template == 'confirm' and coppa.is_coppa_covered(user.birth_month, user.birth_year): |
| 49 | + # Send email only to the sponsor address |
| 50 | + user_email = user.parent_email |
| 51 | + logging.info("COPPA account has a sponsor type of %s", user.parent_email_source) |
| 52 | + |
| 53 | + if user.parent_email_source == SponsorType.TEACHER: |
| 54 | + # Teacher handles the account confirmation |
| 55 | + send_email_template_to_address(user_email, 'confirm-teacher', server, user.locale, params) |
| 56 | + elif user.parent_email_source == SponsorType.PARENT or\ |
| 57 | + user.parent_email_source == SponsorType.GUARDIAN: |
| 58 | + # Parent handles the account confirmation |
| 59 | + send_email_template_to_address(user_email, 'confirm-parent', server, user.locale, params) |
| 60 | + else: |
| 61 | + logging.info("COPPA account %s has invalid sponsor type [%s]", user.id, user.parent_email_source) |
| 62 | + |
| 63 | + return |
| 64 | + elif template == 'reset' and coppa.is_coppa_covered(user.birth_month, user.birth_year): |
| 65 | + # Send email only to the sponsor address |
| 66 | + logging.info("COPPA account has a sponsor type of %s", user.parent_email_source) |
| 67 | + |
| 68 | + # Send password reset to student and parent |
| 69 | + send_email_template_to_address(user.email, 'reset-coppa', server, user.locale, params) |
| 70 | + send_email_template_to_address(user.parent_email, 'reset-coppa', server, user.locale, params) |
| 71 | + return |
| 72 | + else: |
| 73 | + # Registration not subject to COPPA regulations. |
| 74 | + # |
| 75 | + # Evaluate user wanting to use an alternate email address to register |
| 76 | + # the account. |
| 77 | + logging.info('Non-COPPA registration') |
| 78 | + if user.parent_email_source == SponsorType.INDIVIDUAL and user.parent_email: |
| 79 | + user_email = user.parent_email |
| 80 | + logging.info('Individual sponsor email %s being used', user_email) |
| 81 | + |
| 82 | + if user.parent_email: |
| 83 | + user_email = user.parent_email |
| 84 | + logging.info('Sponsor email %s being used', user_email) |
25 | 85 |
|
26 | | - send_email_template_to_address(user.email, template, server, user.locale, params) |
| 86 | + send_email_template_to_address(user_email, template, server, user.locale, params) |
| 87 | + |
| 88 | + return |
27 | 89 |
|
28 | 90 |
|
29 | 91 | def send_email_template_to_address(recipient, template, server, locale, params=None, **kwargs): |
30 | | - # Read templates |
| 92 | + logging.info("Preparing email template: %s for %s", template, recipient) |
31 | 93 | params = params or {} |
| 94 | + |
| 95 | + # Add any supplied arguments to the parameter dictionary |
32 | 96 | for key, value in kwargs.items(): |
33 | 97 | params[key] = value |
| 98 | + |
34 | 99 | params['email'] = recipient |
35 | 100 | params['locale'] = locale |
36 | 101 |
|
| 102 | + # Create a URI-friendly version of the email addresses |
| 103 | + params['email-uri'] = _convert_email_uri(params['email']) |
| 104 | + logging.info("Email address %s converted to %s", |
| 105 | + params['email'], |
| 106 | + params['email-uri'] |
| 107 | + ) |
| 108 | + |
| 109 | + params['registrant-email-uri'] = _convert_email_uri(params['registrant-email']) |
| 110 | + logging.info("Registrant email address %s converted to %s", |
| 111 | + params['registrant-email'], |
| 112 | + params['registrant-email-uri'] |
| 113 | + ) |
| 114 | + |
| 115 | + params['sponsor-email-uri'] = _convert_email_uri(params['sponsoremail']) |
| 116 | + logging.info("Sponsor email address %s converted to %s", |
| 117 | + params['sponsoremail'], |
| 118 | + params['sponsor-email-uri'] |
| 119 | + ) |
| 120 | + |
| 121 | + # Read templates |
37 | 122 | (subject, plain, rich) = _read_templates(template, server, locale, params) |
| 123 | + # Add error checking here to detect any issues with parsing the template. |
38 | 124 |
|
| 125 | + logging.info("Sending email to %s", params['email']) |
39 | 126 | send_email(recipient, subject, plain, rich) |
40 | 127 |
|
41 | 128 |
|
42 | 129 | def send_email(recipient, subject, email_text, rich_email_text=None): |
| 130 | + logging.info('Creating email message package') |
43 | 131 | msg = Message( |
44 | 132 | recipients=[recipient], |
45 | 133 | subject=subject.rstrip(), |
46 | 134 | body=email_text, |
47 | 135 | html=rich_email_text, |
48 | 136 | sender=app.config['DEFAULT_MAIL_SENDER'] |
49 | 137 | ) |
50 | | - mail.send(msg) |
| 138 | + |
| 139 | + # Attempt to send the email |
| 140 | + try: |
| 141 | + logging.info('Sending email message to server') |
| 142 | + mail.send(msg) |
| 143 | + except Exception as ex: |
| 144 | + logging.error('Unable to send email') |
| 145 | + logging.error('Error message: %s', ex.message) |
| 146 | + return 1 |
| 147 | + |
| 148 | + logging.info('Email message was delivered to server') |
| 149 | + return 0 |
51 | 150 |
|
52 | 151 |
|
53 | 152 | def _read_templates(template, server, locale, params): |
| 153 | + logging.info("Loading header text for template: %s", template) |
54 | 154 | header = _read_template(template, server, locale, 'header', params) |
| 155 | + |
| 156 | + logging.info("Loading plain message text for template: %s", template) |
55 | 157 | plain = _read_template(template, server, locale, 'plain', params) |
| 158 | + |
| 159 | + logging.info("Loading rich message text for template: %s", template) |
56 | 160 | rich = _read_template(template, server, locale, 'rich', params, True) |
57 | 161 |
|
58 | 162 | return header, plain, rich |
59 | 163 |
|
60 | 164 |
|
61 | 165 | def _read_template(template, server, locale, part, params, none_if_missing=False): |
| 166 | + """ |
| 167 | + Render a mustache template. |
| 168 | +
|
| 169 | + :param template: Base template name |
| 170 | + :param server: Host server |
| 171 | + :param locale: Language designator |
| 172 | + :param part: Generic message type descriptor |
| 173 | + :param params: Text string to replace tags embedded within the template |
| 174 | + :param none_if_missing: Return 'none' if the requested template is not found |
| 175 | +
|
| 176 | + :return: Upon success, return a Renderer object. Return none or a general |
| 177 | + error message if the none_is_missing flag is false |
| 178 | + """ |
62 | 179 | template_file = expanduser("~/templates/%s/%s/%s/%s.mustache" % (locale, template, server, part)) |
| 180 | + |
63 | 181 | if isfile(template_file): |
64 | 182 | logging.debug('Looking for template file: %s', template_file) |
| 183 | + |
65 | 184 | renderer = pystache.Renderer() |
66 | | - rendered = renderer.render_path(template_file, params) |
67 | | - #print(rendered) |
| 185 | + |
| 186 | + logging.debug('Rendering the template file') |
| 187 | + try: |
| 188 | + rendered = renderer.render_path(template_file, params) |
| 189 | + except Exception as ex: |
| 190 | + logging.error('Unable to render template file %s', template_file) |
| 191 | + logging.error('Error message: %s', ex.message) |
| 192 | + return 'Template format error.' |
| 193 | + |
| 194 | + logging.debug('Returning rendered template file.') |
68 | 195 | return rendered |
69 | 196 | else: |
70 | | - logging.error('Looking for template file: %s, but the file is missing', template_file) |
| 197 | + logging.warn('Looking for template file: %s, but the file is missing', template_file) |
71 | 198 | if none_if_missing: |
72 | 199 | return None |
73 | 200 | else: |
74 | 201 | return 'Template missing' |
| 202 | + |
| 203 | + |
| 204 | +def _convert_email_uri(email): |
| 205 | + """ |
| 206 | + Evaluate email address and replace any plus signs that may appear in the |
| 207 | + portion of the address prior to the '@' with the literal '%2B'. |
| 208 | +
|
| 209 | + Standard web servers will convert any plus ('+') symbol to a space (' ') |
| 210 | + anywhere where they may appear in the URL. This will allow functions upstream |
| 211 | + to create a URI that contains an email address that, when submitted to a |
| 212 | + server, will not be replaced with a space character. |
| 213 | + """ |
| 214 | + if "+" in email: |
| 215 | + return email.replace("+", "%2B") |
| 216 | + else: |
| 217 | + return email |
0 commit comments