@@ -13,7 +13,7 @@ import os, sys, importlib, inspect, json | |||||
from .exceptions import * | from .exceptions import * | ||||
from .utils.jinja import get_jenv, get_template, render_template | from .utils.jinja import get_jenv, get_template, render_template | ||||
__version__ = "7.0.14" | |||||
__version__ = "7.0.15" | |||||
local = Local() | local = Local() | ||||
@@ -16,6 +16,7 @@ class TestEmailAccount(unittest.TestCase): | |||||
def setUp(self): | def setUp(self): | ||||
email_account = frappe.get_doc("Email Account", "_Test Email Account 1") | email_account = frappe.get_doc("Email Account", "_Test Email Account 1") | ||||
email_account.db_set("enable_incoming", 1) | email_account.db_set("enable_incoming", 1) | ||||
frappe.db.sql('delete from `tabEmail Queue`') | |||||
def tearDown(self): | def tearDown(self): | ||||
email_account = frappe.get_doc("Email Account", "_Test Email Account 1") | email_account = frappe.get_doc("Email Account", "_Test Email Account 1") | ||||
@@ -37,7 +37,7 @@ class Newsletter(Document): | |||||
self.validate_send() | self.validate_send() | ||||
# using default queue with a longer timeout as this isn't a scheduled task | # using default queue with a longer timeout as this isn't a scheduled task | ||||
enqueue(send_newsletter, queue='default', timeout=1500, event='send_newsletter', newsletter=self.name) | |||||
enqueue(send_newsletter, queue='default', timeout=3000, event='send_newsletter', newsletter=self.name) | |||||
else: | else: | ||||
self.queue_all() | self.queue_all() | ||||
@@ -135,7 +135,7 @@ def add(email, sender, subject, formatted, text_content=None, | |||||
e.reference_name = reference_name | e.reference_name = reference_name | ||||
e.communication = communication | e.communication = communication | ||||
e.send_after = send_after | e.send_after = send_after | ||||
e.insert(ignore_permissions=True) | |||||
e.db_insert() | |||||
def check_email_limit(recipients): | def check_email_limit(recipients): | ||||
# if using settings from site_config.json, check email limit | # if using settings from site_config.json, check email limit | ||||
@@ -151,6 +151,9 @@ def check_email_limit(recipients): | |||||
monthly_email_limit = frappe.conf.get('limits', {}).get('emails') or 500 | monthly_email_limit = frappe.conf.get('limits', {}).get('emails') or 500 | ||||
if frappe.flags.in_test: | |||||
monthly_email_limit = 500 | |||||
if (this_month + len(recipients)) > monthly_email_limit: | if (this_month + len(recipients)) > monthly_email_limit: | ||||
throw(_("Cannot send this email. You have crossed the sending limit of {0} emails for this month.").format(monthly_email_limit), | throw(_("Cannot send this email. You have crossed the sending limit of {0} emails for this month.").format(monthly_email_limit), | ||||
EmailLimitCrossedError) | EmailLimitCrossedError) | ||||
@@ -244,6 +247,7 @@ def return_unsubscribed_page(email, doctype, name): | |||||
def flush(from_test=False): | def flush(from_test=False): | ||||
"""flush email queue, every time: called from scheduler""" | """flush email queue, every time: called from scheduler""" | ||||
# additional check | # additional check | ||||
cache = frappe.cache() | |||||
check_email_limit([]) | check_email_limit([]) | ||||
auto_commit = not from_test | auto_commit = not from_test | ||||
@@ -251,34 +255,40 @@ def flush(from_test=False): | |||||
msgprint(_("Emails are muted")) | msgprint(_("Emails are muted")) | ||||
from_test = True | from_test = True | ||||
frappe.db.sql("""update `tabEmail Queue` set status='Expired' | |||||
where datediff(curdate(), creation) > 7 and status='Not Sent'""", auto_commit=auto_commit) | |||||
smtpserver = SMTPServer() | smtpserver = SMTPServer() | ||||
for i in xrange(500): | |||||
# don't use for update here, as it leads deadlocks | |||||
email = frappe.db.sql('''select * from `tabEmail Queue` | |||||
where status='Not Sent' and (send_after is null or send_after < %(now)s) | |||||
order by priority desc, creation asc | |||||
limit 1''', { 'now': now_datetime() }, as_dict=True) | |||||
make_cache_queue() | |||||
if email: | |||||
email = email[0] | |||||
else: | |||||
break | |||||
for i in xrange(cache.llen('cache_email_queue')): | |||||
email = cache.lpop('cache_email_queue') | |||||
send_one(email, smtpserver, auto_commit) | |||||
if email: | |||||
send_one(email, smtpserver, auto_commit) | |||||
# NOTE: removing commit here because we pass auto_commit | # NOTE: removing commit here because we pass auto_commit | ||||
# finally: | # finally: | ||||
# frappe.db.commit() | # frappe.db.commit() | ||||
def make_cache_queue(): | |||||
'''cache values in queue before sendign''' | |||||
cache = frappe.cache() | |||||
emails = frappe.db.sql('''select name from `tabEmail Queue` | |||||
where status='Not Sent' and (send_after is null or send_after < %(now)s) | |||||
order by priority desc, creation asc | |||||
limit 500''', { 'now': now_datetime() }) | |||||
# reset value | |||||
cache.delete_value('cache_email_queue') | |||||
for e in emails: | |||||
cache.rpush('cache_email_queue', e[0]) | |||||
def send_one(email, smtpserver=None, auto_commit=True, now=False): | def send_one(email, smtpserver=None, auto_commit=True, now=False): | ||||
'''Send Email Queue with given smtpserver''' | '''Send Email Queue with given smtpserver''' | ||||
status = frappe.db.sql('''select status from `tabEmail Queue` where name=%s for update''', email.name)[0][0] | |||||
if status != 'Not Sent': | |||||
email = frappe.db.sql('''select name, status, communication, | |||||
message, sender, recipient, reference_doctype | |||||
from `tabEmail Queue` where name=%s for update''', email, as_dict=True)[0] | |||||
if email.status != 'Not Sent': | |||||
# rollback to release lock and return | # rollback to release lock and return | ||||
frappe.db.rollback() | frappe.db.rollback() | ||||
return | return | ||||
@@ -337,3 +347,6 @@ def clear_outbox(): | |||||
"""Remove mails older than 31 days in Outbox. Called daily via scheduler.""" | """Remove mails older than 31 days in Outbox. Called daily via scheduler.""" | ||||
frappe.db.sql("""delete from `tabEmail Queue` where | frappe.db.sql("""delete from `tabEmail Queue` where | ||||
datediff(now(), creation) > 31""") | datediff(now(), creation) > 31""") | ||||
frappe.db.sql("""update `tabEmail Queue` set status='Expired' | |||||
where datediff(curdate(), creation) > 7 and status='Not Sent'""") |
@@ -686,17 +686,8 @@ fieldset { | |||||
padding-bottom: 30px; | padding-bottom: 30px; | ||||
} | } | ||||
.blog-comments { | .blog-comments { | ||||
background-color: #fafbfc; | |||||
position: relative; | position: relative; | ||||
} | |||||
.blog-comments:before { | |||||
content: ""; | |||||
background-color: #fafbfc; | |||||
position: absolute; | |||||
height: 100%; | |||||
width: 100vw; | |||||
left: calc((100vw - 100%)/ -2); | |||||
z-index: -1; | |||||
border-top: 1px solid #d1d8dd; | |||||
} | } | ||||
.blog-comment-row { | .blog-comment-row { | ||||
margin: 0px -15px; | margin: 0px -15px; | ||||
@@ -396,18 +396,8 @@ fieldset { | |||||
.help-article-comments { | .help-article-comments { | ||||
} | } | ||||
.blog-comments { | .blog-comments { | ||||
background-color: @light-bg; | |||||
position: relative; | position: relative; | ||||
} | |||||
.blog-comments:before { | |||||
content:""; | |||||
background-color: @light-bg; | |||||
position: absolute; | |||||
height: 100%; | |||||
width: 100vw; | |||||
left: ~"calc((100vw - 100%)/ -2)"; | |||||
z-index: -1; | |||||
border-top: 1px solid @border-color; | |||||
} | } | ||||
.blog-comment-row { | .blog-comment-row { | ||||
@@ -117,6 +117,18 @@ class RedisWrapper(redis.Redis): | |||||
if key in frappe.local.cache: | if key in frappe.local.cache: | ||||
del frappe.local.cache[key] | del frappe.local.cache[key] | ||||
def lpush(self, key, value): | |||||
super(redis.Redis, self).lpush(self.make_key(key), value) | |||||
def rpush(self, key, value): | |||||
super(redis.Redis, self).rpush(self.make_key(key), value) | |||||
def lpop(self, key): | |||||
return super(redis.Redis, self).lpop(self.make_key(key)) | |||||
def llen(self, key): | |||||
return super(redis.Redis, self).llen(self.make_key(key)) | |||||
def hset(self, name, key, value): | def hset(self, name, key, value): | ||||
if not name in frappe.local.cache: | if not name in frappe.local.cache: | ||||
frappe.local.cache[name] = {} | frappe.local.cache[name] = {} | ||||
@@ -174,3 +186,4 @@ class RedisWrapper(redis.Redis): | |||||
except redis.exceptions.ConnectionError: | except redis.exceptions.ConnectionError: | ||||
return [] | return [] | ||||
@@ -25,19 +25,23 @@ def get_secret(): | |||||
def verify_request(): | def verify_request(): | ||||
"""Verify if the incoming signed request if it is correct.""" | """Verify if the incoming signed request if it is correct.""" | ||||
query_string = frappe.request.query_string if hasattr(frappe.request, "query_string") \ | |||||
else frappe.local.flags.signed_query_string | |||||
query_string = frappe.local.flags.signed_query_string or \ | |||||
getattr(frappe.request, 'query_string', None) \ | |||||
params, signature = query_string.split("&_signature=") | |||||
valid = False | |||||
given_signature = hmac.new(params.encode("utf-8")) | |||||
if '&_signature=' in query_string: | |||||
params, signature = query_string.split("&_signature=") | |||||
given_signature.update(get_secret()) | |||||
valid = signature == given_signature.hexdigest() | |||||
given_signature = hmac.new(params.encode("utf-8")) | |||||
given_signature.update(get_secret()) | |||||
valid = signature == given_signature.hexdigest() | |||||
if not valid: | if not valid: | ||||
frappe.respond_as_web_page(_("Invalid Link"), | frappe.respond_as_web_page(_("Invalid Link"), | ||||
_("This link is invalid or expired. Please make sure you have pasted correctly.")) | _("This link is invalid or expired. Please make sure you have pasted correctly.")) | ||||
return valid | return valid | ||||
def get_url(cmd, params, nonce=None, secret=None): | def get_url(cmd, params, nonce=None, secret=None): | ||||