You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

216 lines
6.8 KiB

  1. # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. import webnotes
  5. from webnotes import msgprint, throw, _
  6. from webnotes.utils import scrub_urls
  7. import email.utils
  8. from inlinestyler.utils import inline_css
  9. def get_email(recipients, sender='', msg='', subject='[No Subject]',
  10. text_content = None, footer=None, formatted=None):
  11. """send an html email as multipart with attachments and all"""
  12. email = EMail(sender, recipients, subject)
  13. if (not '<br>' in msg) and (not '<p>' in msg) and (not '<div' in msg):
  14. msg = msg.replace('\n', '<br>')
  15. email.set_html(msg, text_content, footer=footer, formatted=formatted)
  16. return email
  17. class EMail:
  18. """
  19. Wrapper on the email module. Email object represents emails to be sent to the client.
  20. Also provides a clean way to add binary `FileData` attachments
  21. Also sets all messages as multipart/alternative for cleaner reading in text-only clients
  22. """
  23. def __init__(self, sender='', recipients=[], subject='', alternative=0, reply_to=None):
  24. from email.mime.multipart import MIMEMultipart
  25. from email import Charset
  26. Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8')
  27. if isinstance(recipients, basestring):
  28. recipients = recipients.replace(';', ',').replace('\n', '')
  29. recipients = recipients.split(',')
  30. # remove null
  31. recipients = filter(None, (r.strip() for r in recipients))
  32. self.sender = sender
  33. self.reply_to = reply_to or sender
  34. self.recipients = recipients
  35. self.subject = subject
  36. self.msg_root = MIMEMultipart('mixed')
  37. self.msg_multipart = MIMEMultipart('alternative')
  38. self.msg_root.attach(self.msg_multipart)
  39. self.cc = []
  40. self.html_set = False
  41. def set_html(self, message, text_content = None, footer=None, formatted=None):
  42. """Attach message in the html portion of multipart/alternative"""
  43. if not formatted:
  44. formatted = get_formatted_html(self.subject, message, footer)
  45. # this is the first html part of a multi-part message,
  46. # convert to text well
  47. if not self.html_set:
  48. if text_content:
  49. self.set_text(text_content)
  50. else:
  51. self.set_html_as_text(message)
  52. self.set_part_html(formatted)
  53. self.html_set = True
  54. def set_text(self, message):
  55. """
  56. Attach message in the text portion of multipart/alternative
  57. """
  58. from email.mime.text import MIMEText
  59. part = MIMEText(message.encode('utf-8'), 'plain', 'utf-8')
  60. self.msg_multipart.attach(part)
  61. def set_part_html(self, message):
  62. from email.mime.text import MIMEText
  63. part = MIMEText(message.encode('utf-8'), 'html', 'utf-8')
  64. self.msg_multipart.attach(part)
  65. def set_html_as_text(self, html):
  66. """return html2text"""
  67. import HTMLParser
  68. from webnotes.utils.email_lib.html2text import html2text
  69. try:
  70. self.set_text(html2text(html))
  71. except HTMLParser.HTMLParseError:
  72. pass
  73. def set_message(self, message, mime_type='text/html', as_attachment=0, filename='attachment.html'):
  74. """Append the message with MIME content to the root node (as attachment)"""
  75. from email.mime.text import MIMEText
  76. maintype, subtype = mime_type.split('/')
  77. part = MIMEText(message, _subtype = subtype)
  78. if as_attachment:
  79. part.add_header('Content-Disposition', 'attachment', filename=filename)
  80. self.msg_root.attach(part)
  81. def attach_file(self, n):
  82. """attach a file from the `FileData` table"""
  83. from webnotes.utils.file_manager import get_file
  84. res = get_file(n)
  85. if not res:
  86. return
  87. self.add_attachment(res[0], res[1])
  88. def add_attachment(self, fname, fcontent, content_type=None):
  89. """add attachment"""
  90. from email.mime.audio import MIMEAudio
  91. from email.mime.base import MIMEBase
  92. from email.mime.image import MIMEImage
  93. from email.mime.text import MIMEText
  94. import mimetypes
  95. if not content_type:
  96. content_type, encoding = mimetypes.guess_type(fname)
  97. if content_type is None:
  98. # No guess could be made, or the file is encoded (compressed), so
  99. # use a generic bag-of-bits type.
  100. content_type = 'application/octet-stream'
  101. maintype, subtype = content_type.split('/', 1)
  102. if maintype == 'text':
  103. # Note: we should handle calculating the charset
  104. if isinstance(fcontent, unicode):
  105. fcontent = fcontent.encode("utf-8")
  106. part = MIMEText(fcontent, _subtype=subtype, _charset="utf-8")
  107. elif maintype == 'image':
  108. part = MIMEImage(fcontent, _subtype=subtype)
  109. elif maintype == 'audio':
  110. part = MIMEAudio(fcontent, _subtype=subtype)
  111. else:
  112. part = MIMEBase(maintype, subtype)
  113. part.set_payload(fcontent)
  114. # Encode the payload using Base64
  115. from email import encoders
  116. encoders.encode_base64(part)
  117. # Set the filename parameter
  118. if fname:
  119. part.add_header(b'Content-Disposition',
  120. ("attachment; filename=%s" % fname).encode('utf-8'))
  121. self.msg_root.attach(part)
  122. def validate(self):
  123. """validate the email ids"""
  124. from webnotes.utils import validate_email_add
  125. def _validate(email):
  126. """validate an email field"""
  127. if email and not validate_email_add(email):
  128. throw("{email} {msg}".format(**{
  129. "email": email,
  130. "msg": _("is not a valid email id")
  131. }))
  132. return email
  133. if not self.sender:
  134. self.sender = webnotes.conn.get_value('Email Settings', None,
  135. 'auto_email_id') or webnotes.conf.get('auto_email_id') or None
  136. if not self.sender:
  137. msgprint(_("Please specify 'Auto Email Id' in Setup > Email Settings"))
  138. if not "expires_on" in webnotes.conf:
  139. msgprint(_("Alternatively, you can also specify 'auto_email_id' in site_config.json"))
  140. raise webnotes.ValidationError
  141. self.sender = _validate(self.sender)
  142. self.reply_to = _validate(self.reply_to)
  143. for e in self.recipients + (self.cc or []):
  144. _validate(e.strip())
  145. def make(self):
  146. """build into msg_root"""
  147. self.msg_root['Subject'] = self.subject.encode("utf-8")
  148. self.msg_root['From'] = self.sender.encode("utf-8")
  149. self.msg_root['To'] = ', '.join([r.strip() for r in self.recipients]).encode("utf-8")
  150. self.msg_root['Date'] = email.utils.formatdate()
  151. if not self.reply_to:
  152. self.reply_to = self.sender
  153. self.msg_root['Reply-To'] = self.reply_to.encode("utf-8")
  154. if self.cc:
  155. self.msg_root['CC'] = ', '.join([r.strip() for r in self.cc]).encode("utf-8")
  156. def as_string(self):
  157. """validate, build message and convert to string"""
  158. self.validate()
  159. self.make()
  160. return self.msg_root.as_string()
  161. def get_formatted_html(subject, message, footer=None):
  162. message = scrub_urls(message)
  163. return inline_css(webnotes.get_template("templates/emails/standard.html").render({
  164. "content": message,
  165. "footer": get_footer(footer),
  166. "title": subject
  167. }))
  168. def get_footer(footer=None):
  169. """append a footer (signature)"""
  170. footer = footer or ""
  171. # control panel
  172. footer += webnotes.conn.get_value('Control Panel', None, 'mail_footer') or ''
  173. # hooks
  174. for f in webnotes.get_hooks("mail_footer"):
  175. footer += webnotes.get_attr(f)
  176. footer += "<!--unsubscribe link here-->"
  177. return footer