Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 
 

248 rader
7.8 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. import frappe
  5. from frappe.utils.pdf import get_pdf
  6. from frappe.email.smtp import get_outgoing_email_account
  7. from frappe.utils import get_url, scrub_urls, strip, expand_relative_urls, cint
  8. import email.utils
  9. from markdown2 import markdown
  10. def get_email(recipients, sender='', msg='', subject='[No Subject]',
  11. text_content = None, footer=None, print_html=None, formatted=None, attachments=None,
  12. content=None, reply_to=None, cc=()):
  13. """send an html email as multipart with attachments and all"""
  14. content = content or msg
  15. emailobj = EMail(sender, recipients, subject, reply_to=reply_to, cc=cc)
  16. if not content.strip().startswith("<"):
  17. content = markdown(content)
  18. emailobj.set_html(content, text_content, footer=footer, print_html=print_html, formatted=formatted)
  19. if isinstance(attachments, dict):
  20. attachments = [attachments]
  21. for attach in (attachments or []):
  22. emailobj.add_attachment(**attach)
  23. return emailobj
  24. class EMail:
  25. """
  26. Wrapper on the email module. Email object represents emails to be sent to the client.
  27. Also provides a clean way to add binary `FileData` attachments
  28. Also sets all messages as multipart/alternative for cleaner reading in text-only clients
  29. """
  30. def __init__(self, sender='', recipients=(), subject='', alternative=0, reply_to=None, cc=()):
  31. from email.mime.multipart import MIMEMultipart
  32. from email import Charset
  33. Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8')
  34. if isinstance(recipients, basestring):
  35. recipients = recipients.replace(';', ',').replace('\n', '')
  36. recipients = recipients.split(',')
  37. # remove null
  38. recipients = filter(None, (strip(r) for r in recipients))
  39. self.sender = sender
  40. self.reply_to = reply_to or sender
  41. self.recipients = recipients
  42. self.subject = subject
  43. self.msg_root = MIMEMultipart('mixed')
  44. self.msg_multipart = MIMEMultipart('alternative')
  45. self.msg_root.attach(self.msg_multipart)
  46. self.cc = cc or []
  47. self.html_set = False
  48. def set_html(self, message, text_content = None, footer=None, print_html=None, formatted=None):
  49. """Attach message in the html portion of multipart/alternative"""
  50. if not formatted:
  51. formatted = get_formatted_html(self.subject, message, footer, print_html)
  52. # this is the first html part of a multi-part message,
  53. # convert to text well
  54. if not self.html_set:
  55. if text_content:
  56. self.set_text(expand_relative_urls(text_content))
  57. else:
  58. self.set_html_as_text(expand_relative_urls(formatted))
  59. self.set_part_html(formatted)
  60. self.html_set = True
  61. def set_text(self, message):
  62. """
  63. Attach message in the text portion of multipart/alternative
  64. """
  65. from email.mime.text import MIMEText
  66. part = MIMEText(message, 'plain', 'utf-8')
  67. self.msg_multipart.attach(part)
  68. def set_part_html(self, message):
  69. from email.mime.text import MIMEText
  70. part = MIMEText(message, 'html', 'utf-8')
  71. self.msg_multipart.attach(part)
  72. def set_html_as_text(self, html):
  73. """return html2text"""
  74. import HTMLParser
  75. from html2text import html2text
  76. try:
  77. self.set_text(html2text(html))
  78. except HTMLParser.HTMLParseError:
  79. pass
  80. def set_message(self, message, mime_type='text/html', as_attachment=0, filename='attachment.html'):
  81. """Append the message with MIME content to the root node (as attachment)"""
  82. from email.mime.text import MIMEText
  83. maintype, subtype = mime_type.split('/')
  84. part = MIMEText(message, _subtype = subtype)
  85. if as_attachment:
  86. part.add_header('Content-Disposition', 'attachment', filename=filename)
  87. self.msg_root.attach(part)
  88. def attach_file(self, n):
  89. """attach a file from the `FileData` table"""
  90. from frappe.utils.file_manager import get_file
  91. res = get_file(n)
  92. if not res:
  93. return
  94. self.add_attachment(res[0], res[1])
  95. def add_attachment(self, fname, fcontent, content_type=None):
  96. """add attachment"""
  97. from email.mime.audio import MIMEAudio
  98. from email.mime.base import MIMEBase
  99. from email.mime.image import MIMEImage
  100. from email.mime.text import MIMEText
  101. import mimetypes
  102. if not content_type:
  103. content_type, encoding = mimetypes.guess_type(fname)
  104. if content_type is None:
  105. # No guess could be made, or the file is encoded (compressed), so
  106. # use a generic bag-of-bits type.
  107. content_type = 'application/octet-stream'
  108. maintype, subtype = content_type.split('/', 1)
  109. if maintype == 'text':
  110. # Note: we should handle calculating the charset
  111. if isinstance(fcontent, unicode):
  112. fcontent = fcontent.encode("utf-8")
  113. part = MIMEText(fcontent, _subtype=subtype, _charset="utf-8")
  114. elif maintype == 'image':
  115. part = MIMEImage(fcontent, _subtype=subtype)
  116. elif maintype == 'audio':
  117. part = MIMEAudio(fcontent, _subtype=subtype)
  118. else:
  119. part = MIMEBase(maintype, subtype)
  120. part.set_payload(fcontent)
  121. # Encode the payload using Base64
  122. from email import encoders
  123. encoders.encode_base64(part)
  124. # Set the filename parameter
  125. if fname:
  126. part.add_header(b'Content-Disposition',
  127. ("attachment; filename=\"%s\"" % fname).encode('utf-8'))
  128. self.msg_root.attach(part)
  129. def add_pdf_attachment(self, name, html, options=None):
  130. self.add_attachment(name, get_pdf(html, options), 'application/octet-stream')
  131. def get_default_sender(self):
  132. email_account = get_outgoing_email_account()
  133. return email.utils.formataddr((email_account.name, email_account.get("sender") or email_account.get("email_id")))
  134. def validate(self):
  135. """validate the email ids"""
  136. from frappe.utils import validate_email_add
  137. if not self.sender:
  138. self.sender = self.get_default_sender()
  139. validate_email_add(strip(self.sender), True)
  140. self.reply_to = validate_email_add(strip(self.reply_to) or self.sender, True)
  141. self.recipients = [strip(r) for r in self.recipients]
  142. self.cc = [strip(r) for r in self.cc]
  143. for e in self.recipients + (self.cc or []):
  144. validate_email_add(e, True)
  145. def set_message_id(self, message_id):
  146. self.msg_root["Message-Id"] = "<{0}@{1}>".format(message_id, frappe.local.site)
  147. def make(self):
  148. """build into msg_root"""
  149. headers = {
  150. "Subject": strip(self.subject).encode("utf-8"),
  151. "From": self.sender.encode("utf-8"),
  152. "To": ', '.join(self.recipients).encode("utf-8"),
  153. "Date": email.utils.formatdate(),
  154. "Reply-To": self.reply_to.encode("utf-8") if self.reply_to else None,
  155. "CC": ', '.join(self.cc).encode("utf-8") if self.cc else None,
  156. b'X-Frappe-Site': get_url().encode('utf-8')
  157. }
  158. # reset headers as values may be changed.
  159. for key, val in headers.iteritems():
  160. if self.msg_root.has_key(key):
  161. del self.msg_root[key]
  162. self.msg_root[key] = val
  163. def as_string(self):
  164. """validate, build message and convert to string"""
  165. self.validate()
  166. self.make()
  167. return self.msg_root.as_string()
  168. def get_formatted_html(subject, message, footer=None, print_html=None):
  169. # imported here to avoid cyclic import
  170. message = scrub_urls(message)
  171. rendered_email = frappe.get_template("templates/emails/standard.html").render({
  172. "content": message,
  173. "footer": get_footer(footer),
  174. "title": subject,
  175. "print_html": print_html,
  176. "subject": subject
  177. })
  178. return rendered_email
  179. def get_footer(footer=None):
  180. """append a footer (signature)"""
  181. footer = footer or ""
  182. email_account = get_outgoing_email_account(False)
  183. if email_account and email_account.add_signature and email_account.signature:
  184. footer += email_account.signature
  185. if email_account and email_account.footer:
  186. footer += email_account.footer
  187. footer += "<!--unsubscribe link here-->"
  188. company_address = frappe.db.get_default("email_footer_address")
  189. if company_address:
  190. footer += '<div>{0}</div>'.format(company_address)
  191. if not cint(frappe.db.get_default("disable_standard_email_footer")):
  192. for default_mail_footer in frappe.get_hooks("default_mail_footer"):
  193. footer += default_mail_footer
  194. return footer