@@ -13,7 +13,7 @@ import os, sys, importlib, inspect, json | |||
from .exceptions import * | |||
from .utils.jinja import get_jenv, get_template, render_template | |||
__version__ = "7.0.14" | |||
__version__ = "7.0.15" | |||
local = Local() | |||
@@ -16,6 +16,7 @@ class TestEmailAccount(unittest.TestCase): | |||
def setUp(self): | |||
email_account = frappe.get_doc("Email Account", "_Test Email Account 1") | |||
email_account.db_set("enable_incoming", 1) | |||
frappe.db.sql('delete from `tabEmail Queue`') | |||
def tearDown(self): | |||
email_account = frappe.get_doc("Email Account", "_Test Email Account 1") | |||
@@ -37,7 +37,7 @@ class Newsletter(Document): | |||
self.validate_send() | |||
# 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: | |||
self.queue_all() | |||
@@ -135,7 +135,7 @@ def add(email, sender, subject, formatted, text_content=None, | |||
e.reference_name = reference_name | |||
e.communication = communication | |||
e.send_after = send_after | |||
e.insert(ignore_permissions=True) | |||
e.db_insert() | |||
def check_email_limit(recipients): | |||
# 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 | |||
if frappe.flags.in_test: | |||
monthly_email_limit = 500 | |||
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), | |||
EmailLimitCrossedError) | |||
@@ -244,6 +247,7 @@ def return_unsubscribed_page(email, doctype, name): | |||
def flush(from_test=False): | |||
"""flush email queue, every time: called from scheduler""" | |||
# additional check | |||
cache = frappe.cache() | |||
check_email_limit([]) | |||
auto_commit = not from_test | |||
@@ -251,34 +255,40 @@ def flush(from_test=False): | |||
msgprint(_("Emails are muted")) | |||
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() | |||
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 | |||
# finally: | |||
# 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): | |||
'''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 | |||
frappe.db.rollback() | |||
return | |||
@@ -337,3 +347,6 @@ def clear_outbox(): | |||
"""Remove mails older than 31 days in Outbox. Called daily via scheduler.""" | |||
frappe.db.sql("""delete from `tabEmail Queue` where | |||
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; | |||
} | |||
.blog-comments { | |||
background-color: #fafbfc; | |||
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 { | |||
margin: 0px -15px; | |||
@@ -396,18 +396,8 @@ fieldset { | |||
.help-article-comments { | |||
} | |||
.blog-comments { | |||
background-color: @light-bg; | |||
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 { | |||
@@ -117,6 +117,18 @@ class RedisWrapper(redis.Redis): | |||
if key in frappe.local.cache: | |||
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): | |||
if not name in frappe.local.cache: | |||
frappe.local.cache[name] = {} | |||
@@ -174,3 +186,4 @@ class RedisWrapper(redis.Redis): | |||
except redis.exceptions.ConnectionError: | |||
return [] | |||
@@ -25,19 +25,23 @@ def get_secret(): | |||
def verify_request(): | |||
"""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: | |||
frappe.respond_as_web_page(_("Invalid Link"), | |||
_("This link is invalid or expired. Please make sure you have pasted correctly.")) | |||
return valid | |||
def get_url(cmd, params, nonce=None, secret=None): | |||