extended sharing to allow reply-to address and message. Also rolled feedback emails into mail_queue

This commit is contained in:
KeyserSosa
2008-07-28 16:02:24 -07:00
parent 972ab07c35
commit 1227cfcbf0
15 changed files with 321 additions and 123 deletions

View File

@@ -123,7 +123,7 @@ def make_map(global_conf={}, app_conf={}):
action='resetpassword')
mc('/post/:action', controller='post',
requirements=dict(action="options|over18|unlogged_options"))
requirements=dict(action="options|over18|unlogged_options|optout|optin"))
mc('/api/:action', controller='api')
mc('/d/:what', controller='api', action='bookmarklet')

View File

@@ -124,19 +124,18 @@ class ApiController(RedditController):
if not res.error:
if reason != 'ad_inq':
emailer.feedback_email(email, message, name = name or '')
emailer.feedback_email(email, message, name = name or '',
reply_to = replyto or '')
else:
emailer.ad_inq_email(email, message, name = name or '')
emailer.ad_inq_email(email, message, name = name or '',
reply_to = replyto or '')
res._update('success',
innerHTML=_("thanks for your message! you should hear back from us shortly."))
res._update("personal", value='')
res._update("captcha", value='')
res._hide("wtf")
POST_ad_inq = POST_feedback
@Json
@validate(VUser(),
VModhash(),
@@ -439,7 +438,7 @@ class ApiController(RedditController):
@validate(VUser('curpass', default = ''),
VModhash(),
curpass = nop('curpass'),
email = nop("email"),
email = ValidEmails("email", num = 1),
newpass = nop("newpass"),
verpass = nop("verpass"),
password = VPassword(['newpass', 'verpass']))
@@ -450,8 +449,10 @@ class ApiController(RedditController):
res._update('curpass', value='')
return
updated = False
if email and (not hasattr(c.user,'email')
or c.user.email != email):
if res._chk_error(errors.BAD_EMAILS):
res._focus('email')
elif email and (not hasattr(c.user,'email')
or c.user.email != email):
c.user.email = email
c.user._commit()
res._update('status',
@@ -612,16 +613,20 @@ class ApiController(RedditController):
if should_ratelimit:
VRatelimit.ratelimit(rate_user=True, rate_ip = True, prefix = "rate_comment_")
@Json
@validate(VUser(),
VModhash(),
VCaptcha(),
VRatelimit(rate_user = True, rate_ip = True,
prefix = "rate_share_"),
share_from = VLength('share_from', length = 60),
share_from = VLength('share_from', length = 100),
emails = ValidEmails("share_to"),
reply_to = ValidEmails("replyto", num = 1),
message = VLength("message", length = 1000),
thing = VByName('id'))
def POST_share(self, res, emails, thing, share_from):
def POST_share(self, res, emails, thing, share_from, reply_to,
message):
# remove the ratelimit error if the user's karma is high
sr = thing.subreddit_slow
@@ -633,18 +638,37 @@ class ApiController(RedditController):
if res._chk_captcha(errors.BAD_CAPTCHA, thing._fullname):
pass
elif not res._chk_errors((errors.BAD_EMAILS, errors.NO_EMAILS,
errors.RATELIMIT, errors.TOO_MANY_EMAILS),
thing._fullname):
elif res._chk_error(errors.RATELIMIT, thing._fullname):
pass
elif (share_from is None and
res._chk_error(errors.COMMENT_TOO_LONG,
'share_from_' + thing._fullname)):
res._focus('share_from_' + thing._fullname)
elif (message is None and
res._chk_error(errors.COMMENT_TOO_LONG,
'message_' + thing._fullname)):
res._focus('message_' + thing._fullname)
elif not emails and res._chk_errors((errors.BAD_EMAILS,
errors.NO_EMAILS,
errors.TOO_MANY_EMAILS),
"emails_" + thing._fullname):
res._focus("emails_" + thing._fullname)
elif not reply_to and res._chk_error(errors.BAD_EMAILS,
"replyto_" + thing._fullname):
res._focus("replyto_" + thing._fullname)
else:
c.user.add_share_emails(emails)
c.user._commit()
res._update("share_li_" + thing._fullname,
innerHTML=_('shared'))
res._hide("sharelink_" + thing._fullname)
emailer.share(thing, emails, from_name = share_from or "")
res._update("sharelink_" + thing._fullname,
innerHTML=("<div class='clearleft'></div><p class='error'>%s</p>" %
_("your link has been shared.")))
emailer.share(thing, emails, from_name = share_from or "",
body = message or "", reply_to = reply_to or "")
#set the ratelimiter
if should_ratelimit:

View File

@@ -28,7 +28,7 @@ from r2.models import *
from r2.lib.pages import *
from r2.lib.menus import *
from r2.lib.utils import to36, sanitize_url, check_cheating, title_to_url, query_string
from r2.lib.emailer import opt_out, opt_in
from r2.lib.emailer import has_opted_out, Email
from r2.lib.db.operators import desc
from r2.lib.strings import strings
import r2.lib.db.thing as thing
@@ -564,24 +564,30 @@ class FrontController(RedditController):
subreddits = sr_names,
captcha=captcha)).render()
@validate(msg_hash = nop('x'))
def GET_optout(self, msg_hash):
email, sent = opt_out(msg_hash)
def _render_opt_in_out(self, msg_hash, leave):
"""Generates the form for an optin/optout page"""
email = Email.handler.get_recipient(msg_hash)
if not email:
return self.abort404()
return BoringPage(_("opt out"),
content = OptOut(email = email, leave = True,
sent = (has_opted_out(email) == leave)
return BoringPage(_("opt out") if leave else _("welcome back"),
content = OptOut(email = email, leave = leave,
sent = sent,
msg_hash = msg_hash)).render()
@validate(msg_hash = nop('x'))
def GET_optout(self, msg_hash):
"""handles /mail/optout to add an email to the optout mailing
list. The actual email addition comes from the user posting
the subsequently rendered form and is handled in
ApiController.POST_optout."""
return self._render_opt_in_out(msg_hash, True)
@validate(msg_hash = nop('x'))
def GET_optin(self, msg_hash):
email, sent = opt_in(msg_hash)
if not email:
return self.abort404()
return BoringPage(_("welcome back"),
content = OptOut(email = email, leave = False,
sent = sent,
msg_hash = msg_hash)).render()
"""handles /mail/optin to remove an email address from the
optout list. The actual email removal comes from the user
posting the subsequently rendered form and is handled in
ApiController.POST_optin."""
return self._render_opt_in_out(msg_hash, False)

View File

@@ -22,6 +22,7 @@
from r2.lib.pages import *
from api import ApiController
from r2.lib.utils import Storage, query_string
from r2.lib.emailer import opt_in, opt_out
from pylons import request, c, g
from validator import *
from pylons.i18n import _
@@ -125,3 +126,26 @@ class PostController(ApiController):
return self.redirect(dest)
else:
return self.redirect('/')
@validate(msg_hash = nop('x'))
def POST_optout(self, msg_hash):
email, sent = opt_out(msg_hash)
if not email:
return self.abort404()
return BoringPage(_("opt out"),
content = OptOut(email = email, leave = True,
sent = True,
msg_hash = msg_hash)).render()
@validate(msg_hash = nop('x'))
def POST_optin(self, msg_hash):
email, sent = opt_in(msg_hash)
if not email:
return self.abort404()
return BoringPage(_("welcome back"),
content = OptOut(email = email, leave = False,
sent = True,
msg_hash = msg_hash)).render()

View File

@@ -291,7 +291,8 @@ class RedditController(BaseController):
kw = {}
argspec = inspect.getargspec(fn)
#if there is a **kw argument in the fn definition, just pass along the environment
# if there is a **kw argument in the fn definition,
# just pass along the environment
if argspec[2]:
kw = env
#else for each entry in the arglist set the value from the environment

View File

@@ -641,6 +641,11 @@ class VReason(Validator):
class ValidEmails(Validator):
"""Validates a list of email addresses passed in as a string and
delineated by whitespace, ',' or ';'. Also validates quantity of
provided emails. Returns a list of valid email addresses on
success"""
separator = re.compile(r'[^\s,;]+')
email_re = re.compile(r'.+@.+\..+')
@@ -648,18 +653,32 @@ class ValidEmails(Validator):
self.num = num
Validator.__init__(self, param = param, **kw)
def run(self, emails):
emails = set(self.separator.findall(emails) if emails else [])
def run(self, emails0):
emails = set(self.separator.findall(emails0) if emails0 else [])
failures = set(e for e in emails if not self.email_re.match(e))
emails = emails - failures
if failures:
c.errors.add(errors.BAD_EMAILS, {'emails': ', '.join(failures)})
# make sure the number of addresses does not exceed the max
if self.num > 0 and len(emails) + len(failures) > self.num:
# special case for 1: there should be no delineators at all, so
# send back original string to the user
if self.num == 1:
c.errors.add(errors.BAD_EMAILS,
{'emails': '"%s"' % emails0})
# else report the number expected
else:
c.errors.add(errors.TOO_MANY_EMAILS,
{'num': self.num})
# correct number, but invalid formatting
elif failures:
c.errors.add(errors.BAD_EMAILS,
{'emails': ', '.join(failures)})
# no emails
elif not emails:
c.errors.add(errors.NO_EMAILS)
elif len(emails) > self.num:
c.errors.add(errors.TOO_MANY_EMAILS, {'num': self.num})
else:
return emails
# return single email if one is expected, list otherwise
return list(emails)[0] if self.num == 1 else emails
# NOTE: make sure *never* to have res check these are present
# otherwise, the response could contain reference to these errors...!

View File

@@ -24,7 +24,7 @@ from pylons.i18n import _
from pylons import c, g, request
from r2.lib.pages import PasswordReset, Share, Mail_Opt
from r2.lib.utils import timeago
from r2.models import passhash, Email, Default
from r2.models import passhash, Email, Default, has_opted_out
from r2.config import cache
import os, random, datetime
import smtplib, traceback, sys
@@ -48,25 +48,6 @@ def simple_email(to, fr, subj, body):
msg['Subject'] = utf8(subj)
send_mail(msg, fr, to)
def sys_email(email, body, name='', subj = lambda x: x):
fr = (c.user.name if c.user else 'Anonymous user')
if name and name != fr:
fr = "%s [%s]" % (name, fr)
fr = email_address(fr, email)
subj = subj(fr)
simple_email(feedback, fr, subj, body)
def feedback_email(email, body, name=''):
sys_email(email, body, name=name,
subj = lambda fr: "[feedback] feedback from %s" % fr)
def ad_inq_email(email, body, name=''):
sys_email(email, body, name=name,
subj = lambda fr: "[ad_inq] ad inquiry from %s" % fr)
def password_email(user):
key = passhash(random.randint(0, 1000), user.email)
passlink = 'http://' + c.domain + '/resetpassword/' + key
@@ -75,69 +56,110 @@ def password_email(user):
'reddit.com password reset',
PasswordReset(user=user, passlink=passlink).render(style='email'))
def share(link, emails, from_name = ""):
def _feedback_email(email, body, kind, name='', reply_to = ''):
"""Function for handling feedback and ad_inq emails. Adds an
email to the mail queue to the feedback email account."""
Email.handler.add_to_queue(c.user if c.user_is_loggedin else None,
None, [feedback], name, email,
datetime.datetime.now(),
request.ip, kind, body = body,
reply_to = reply_to)
def feedback_email(email, body, name='', reply_to = ''):
"""Queues a feedback email to the feedback account."""
return _feedback_email(email, body, Email.Kind.FEEDBACK, name = name,
reply_to = reply_to)
def ad_inq_email(email, body, name='', reply_to = ''):
"""Queues a ad_inq email to the feedback account."""
return _feedback_email(email, body, Email.Kind.ADVERTISE, name = name,
reply_to = reply_to)
def share(link, emails, from_name = "", reply_to = "", body = ""):
"""Queues a 'share link' email."""
now = datetime.datetime.now(g.tz)
ival = now - timeago(g.new_link_share_delay)
date = max(now,link._date + ival)
Email.handler.add_to_queue(c.user, link, emails, from_name, date,
request.ip, Email.Kind.SHARE)
Email.handler.add_to_queue(c.user, link, emails, from_name, g.share_reply,
date, request.ip, Email.Kind.SHARE,
body = body, reply_to = reply_to)
def send_queued_mail():
"""sends mail from the mail queue to smtplib for delivery. Also,
on successes, empties the mail queue and adds all emails to the
sent_mail list."""
now = datetime.datetime.now(g.tz)
if not c.site:
c.site = Default
clear = False
session = smtplib.SMTP(g.smtp_server)
# convienence funciton for sending the mail to the singly-defined session and
# marking the mail as read.
def sendmail(email):
try:
session.sendmail(email.fr_addr, email.to_addr,
email.to_MIMEText().as_string())
email.set_sent()
# exception happens only for local recipient that doesn't exist
except smtplib.SMTPRecipientsRefused:
# handle error and print, but don't stall the rest of the queue
print "Handled error sending mail (traceback to follow)"
traceback.print_exc(file = sys.stdout)
try:
for email in Email.get_unsent(now):
clear = True
if not email.should_queue():
continue
elif email.kind == Email.Kind.SHARE:
email.fr_addr = g.share_reply
should_queue = email.should_queue()
# check only on sharing that the mail is invalid
if email.kind == Email.Kind.SHARE and should_queue:
email.body = Share(username = email.from_name(),
msg_hash = email.msg_hash,
link = email.thing).render(style = "email")
link = email.thing,
body = email.body).render(style = "email")
email.subject = _("[reddit] %(user)s has shared a link with you") % \
{"user": email.from_name()}
try:
session.sendmail(email.fr_addr, email.to_addr,
email.to_MIMEText().as_string())
except smtplib.SMTPRecipientsRefused:
# handle error but don't stop the queue
print "Handled error sending mail (traceback to follow)"
traceback.print_exc(file = sys.stdout)
sendmail(email)
elif email.kind == Email.Kind.OPTOUT:
email.fr_addr = g.share_reply
email.body = Mail_Opt(msg_hash = email.msg_hash,
leave = True).render(style = "email")
email.subject = _("[reddit] email removal notice")
session.sendmail(email.fr_addr, email.to_addr,
email.to_MIMEText().as_string())
sendmail(email)
elif email.kind == Email.Kind.OPTIN:
email.fr_addr = g.share_reply
email.body = Mail_Opt(msg_hash = email.msg_hash,
leave = False).render(style = "email")
email.subject = _("[reddit] email addition notice")
session.sendmail(email.fr_addr, email.to_addr,
email.to_MIMEText().as_string())
sendmail(email)
elif email.kind in (Email.Kind.FEEDBACK, Email.Kind.ADVERTISE):
if email.kind == Email.Kind.FEEDBACK:
email.subject = "[feedback] feedback from '%s'" % \
email.from_name()
else:
email.subject = "[ad_inq] feedback from '%s'" % \
email.from_name()
sendmail(email)
# handle other types of emails here
else:
# handle other types of emails here
pass
email.set_sent()
finally:
session.quit()
# clear is true if anything was found and processed above
if clear:
Email.handler.clear_queue(now)
def opt_out(msg_hash):
"""Queues an opt-out email (i.e., a confirmation that the email
address has been opted out of receiving any future mail)"""
email, added = Email.handler.opt_out(msg_hash)
if email and added:
Email.handler.add_to_queue(None, None, [email], "reddit.com",
@@ -146,6 +168,8 @@ def opt_out(msg_hash):
return email, added
def opt_in(msg_hash):
"""Queues an opt-in email (i.e., that the email has been removed
from our opt out list)"""
email, removed = Email.handler.opt_in(msg_hash)
if email and removed:
Email.handler.add_to_queue(None, None, [email], "reddit.com",

View File

@@ -26,7 +26,7 @@ from builder import *
from vote import *
from report import *
from subreddit import *
from mail_queue import Email
from mail_queue import Email, has_opted_out, opt_count
from admintools import *
import thing_changes

View File

@@ -50,6 +50,12 @@ def mail_queue(metadata):
# the "To" address of the email
sa.Column('to_addr', sa.String),
# the "From" address of the email
sa.Column('fr_addr', sa.String),
# the "Reply-To" address of the email
sa.Column('reply_to', sa.String),
# fullname of the thing
sa.Column('fullname', sa.String),
@@ -80,6 +86,12 @@ def sent_mail_table(metadata):
# the "To" address of the email
sa.Column('to_addr', sa.String),
# the "From" address of the email
sa.Column('fr_addr', sa.String),
# the "reply-to" address of the email
sa.Column('reply_to', sa.String),
# IP of original request
sa.Column('ip', PGInet),
@@ -142,6 +154,7 @@ class EmailHandler(object):
res = s.execute()
return bool(res.fetchall())
def opt_out(self, msg_hash):
"""Adds the recipient of the email to the opt-out list and returns
that address."""
@@ -152,6 +165,7 @@ class EmailHandler(object):
o.insert().execute({o.c.email: email, o.c.msg_hash: msg_hash})
clear_memo('r2.models.mail_queue.has_opted_out',
email)
clear_memo('r2.models.mail_queue.opt_count')
return (email, True)
except sa.exceptions.SQLError:
return (email, False)
@@ -166,6 +180,7 @@ class EmailHandler(object):
sa.delete(o, o.c.email == email).execute()
clear_memo('r2.models.mail_queue.has_opted_out',
email)
clear_memo('r2.models.mail_queue.opt_count')
return (email, True)
else:
return (email, False)
@@ -178,8 +193,8 @@ class EmailHandler(object):
return res[0][0] if res and res[:1] else None
def add_to_queue(self, user, thing, emails, from_name, date, ip,
kind, body = ""):
def add_to_queue(self, user, thing, emails, from_name, fr_addr, date, ip,
kind, body = "", reply_to = ""):
s = self.queue_table
hashes = []
for email in emails:
@@ -190,6 +205,8 @@ class EmailHandler(object):
s.insert().execute({s.c.to_addr : email,
s.c.account_id : uid,
s.c.from_name : from_name,
s.c.fr_addr : fr_addr,
s.c.reply_to : reply_to,
s.c.fullname: tid,
s.c.ip : ip,
s.c.kind: kind,
@@ -215,7 +232,7 @@ class EmailHandler(object):
res = sa.select([s.c.to_addr, s.c.account_id,
s.c.from_name, s.c.fullname, s.c.body,
s.c.kind, s.c.ip, s.c.date, s.c.uid,
s.c.msg_hash],
s.c.msg_hash, s.c.fr_addr, s.c.reply_to],
sa.and_(*where),
order_by = s.c.uid, limit = batch_limit).execute()
res = res.fetchall()
@@ -242,10 +259,11 @@ class EmailHandler(object):
# did we not fetch them all?
keep_trying = (len(res) == batch_limit)
for addr, acct, fname, fulln, body, kind, ip, date, uid, msg_hash \
in res:
for (addr, acct, fname, fulln, body, kind, ip, date, uid,
msg_hash, fr_addr, reply_to) in res:
yield (accts.get(acct), things.get(fulln), addr,
fname, date, ip, ips[ip], kind, msg_hash, body)
fname, date, ip, ips[ip], kind, msg_hash, body,
fr_addr, reply_to)
def clear_queue(self, max_date, kind = None):
s = self.queue_table
@@ -262,7 +280,8 @@ class Email(object):
Kind = Storage((e, i) for i, e in enumerate(Kind))
def __init__(self, user, thing, email, from_name, date, ip, banned_ip,
kind, msg_hash, body = '', subject = "", from_addr = ''):
kind, msg_hash, body = '', from_addr = '',
reply_to = ''):
self.user = user
self.thing = thing
self.to_addr = email
@@ -273,14 +292,20 @@ class Email(object):
self.banned_ip = banned_ip
self.kind = kind
self.sent = False
self.body = ""
self.subject = subject
self.body = body
self.subject = ''
self.msg_hash = msg_hash
self.reply_to = reply_to
def from_name(self):
return ("%(name)s (%(uname)s)" if self._from_name != self.user.name
else "%(uname)s") % \
dict(name = self._from_name, uname = self.user.name)
if not self.user:
name = "%(name)s"
elif self._from_name != self.user.name:
name = "%(name)s (%(uname)s)"
else:
name = "%(uname)s"
return name % dict(name = self._from_name,
uname = self.user.name if self.user else '')
@classmethod
def get_unsent(cls, max_date, batch_limit = 50, kind = None):
@@ -303,6 +328,8 @@ class Email(object):
t.insert().execute({t.c.account_id:
self.user._id if self.user else 0,
t.c.to_addr : self.to_addr,
t.c.fr_addr : self.fr_addr,
t.c.reply_to : self.reply_to,
t.c.ip : self.ip,
t.c.fullname:
self.thing._fullname if self.thing else "",
@@ -315,7 +342,7 @@ class Email(object):
def to_MIMEText(self):
def utf8(s):
return s.encode('utf8') if isinstance(s, unicode) else s
fr = '"%s" <%s>' % (self._from_name, self.fr_addr) if self._from_name else self.fr_addr
fr = '"%s" <%s>' % (self.from_name(), self.fr_addr)
if not fr.startswith('-') and not self.to_addr.startswith('-'): # security
msg = MIMEText(utf8(self.body))
msg.set_charset('utf8')
@@ -325,6 +352,8 @@ class Email(object):
if self.user:
msg['X-Reddit-username'] = utf8(self.user.name)
msg['X-Reddit-ID'] = self.msg_hash
if self.reply_to:
msg['Reply-To'] = utf8(self.reply_to)
return msg
return None
@@ -336,6 +365,12 @@ def has_opted_out(email):
return bool(res.fetchall())
@memoize('r2.models.mail_queue.opt_count')
def opt_count():
o = Email.handler.opt_table
s = sa.select([sa.func.count(o.c.email)])
res = s.execute().fetchone()
return int(res[0])

View File

@@ -826,7 +826,9 @@ a.star { text-decoration: none; color: #ff8b60 }
.clearleft { clear: left; height: 0px; }
.clear { clear: both; }
.sharetable {margin-left: 20px; }
.sharetable.preftable {margin-left: 20px; }
.sharetable.preftable th { padding-bottom: 5px; padding-top: 5px; }
.sharetable.preftable button { margin-top: 10px }
.sponsored .entry { margin-right: 20px;}
@@ -1229,6 +1231,10 @@ a.star { text-decoration: none; color: #ff8b60 }
#passform.pretty-form button { padding: 0px 1px; }
/*opt-out/in form*/
.opt-form { font-size: larger }
.opt-form form { display: inline; }
.preftable th {
padding: 10px;
font-weight: bold;

View File

@@ -68,9 +68,10 @@
<td>
%if thing.replyto:
<input id="replyto" name="Reply-to" type="text"
value="${thing.email}" onfocus="clearTitle(this)" size="30"/>
value="${thing.email}" onfocus="clearTitle(this)"
size="30"/>
%else:
<input id="replyto" name="email" style="color: gray"
<input id="replyto" name="replyto" style="color: gray"
value="${_('optional')}" type="text"
onfocus="clearTitle(this)" size="30"/>
%endif

View File

@@ -19,28 +19,41 @@
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved."
###############################################################################
<div style="font-size:larger">
<div class="opt-form">
%if thing.leave:
<p>
${_("The address %(email)s will no longer recieve email from us.") % dict(email=thing.email)}
</p>
%if thing.sent:
<p>
<p>
${_("The address '%(email)s' will no longer receive email from us.") % dict(email=thing.email)}
</p>
<p>
${_("A confirmation email has been queued up and should be reaching you shortly. It will be the last you hear of us.")}
</p>
%else:
<p>
${_("A confirmation email has already been queued up and/or sent.")}
${_("Would you like the address %(email)s to no longer receive email from us?") % dict(email=thing.email)}
<form method="post" action="/post/optout">
<input type="hidden" name="x" value="${thing.msg_hash}" />
<input type="submit" name="accept" value="${_('yes')}" />
</form>
<form method="get" action="/">
<input type="submit" name="decline" value="${_('no')}" />
</form>
</p>
%endif
%elif thing.sent:
<p>
${_("The address %(email)s is once again fair game to receive email from us. Welcome back. You'll be receiving a confirmation email.") % dict(email=thing.email)}
${_("'%(email)s' has been removed from our block list.") % dict(email=thing.email)}
</p>
%else:
<p>
${_("%(email)s has already been removed from our block list.") % dict(email=thing.email)}
<p>
${_("Allow '%(email)s' to receive email from us?") % dict(email=thing.email)}
<form method="post" action="/post/optin">
<input type="hidden" name="x" value="${thing.msg_hash}" />
<input type="submit" name="accept" value="${_('yes')}" />
</form>
<form method="get" action="/">
<input type="submit" name="decline" value="${_('no')}" />
</form>
</p>
%endif
</div>

View File

@@ -17,7 +17,7 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
## CondeNet, Inc. All Rights Reserved."
################################################################################
<%namespace file="utils.html" import="error_field"/>
@@ -33,7 +33,9 @@
<tr>
<td align="right">${_("email")}:</td>
<td><input name="email" type="text" value="${hasattr(c.user,'email') and c.user.email or ''}"/></td>
<td></td>
<td>
${error_field("BAD_EMAILS")}
</td>
</tr>
<tr>
<td align="right">${_("new password")}:</td>

View File

@@ -19,14 +19,18 @@
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
################################################################################
<%! from r2.lib.template_helpers import reddit_link %>A user from http://${g.domain}/ has shared a link with you.
%if not thing.body:
${thing.username} from http://${g.domain}/ has shared a link with you.
%else:
${thing.body}
%endif
"${thing.link.title}"
http://${g.domain}/goto?share=true&id=${thing.link._fullname}
There are currently ${thing.link.num_comments} comments on this link. You can view them here:
<% from r2.lib.strings import strings, plurals %>${_("There are currently %(num_comments)s on this link. You can view them here:") % dict(num_comments = strings.number_label % (thing.link.num_comments, plurals.N_comments(thing.link.num_comments)))}
http://${g.domain}${reddit_link(thing.link.permalink, url=True)}
<% from r2.lib.template_helpers import reddit_link %>http://${g.domain}${reddit_link(thing.link.permalink, url=True)}
___
If you would not like to receive emails from reddit.com in the future, visit http://${g.domain}/mail/optout?x=${thing.msg_hash}

View File

@@ -2,16 +2,17 @@
<%namespace file="utils.html" import="error_field"/>
<%namespace file="captcha.html" import="captchagen"/>
<div id="sharelink_${thing.link_name}"
<div id="sharelink_${thing.link_name}"
${"" if thing.link_name else "style='display:none'"}>
<div class="clearleft"><!--IEsux--></div>
<form onsubmit="return post_form(this, 'share')" method="post"
id="shareform_${thing.link_name}" class="pretty-form"
action="/post/share">
${error_field("RATELIMIT_"+ thing.link_name)}
<table class="preftable sharetable">
<table class="sharetable preftable">
<tr>
<th>
<label class="big" for="share_to_${thing.link_name}">
<label for="share_to_${thing.link_name}">
${_("send this link to")}
</label>
</th>
@@ -21,14 +22,14 @@
</textarea>
</td>
<td>
${error_field("BAD_EMAILS_" + thing.link_name)}
${error_field("TOO_MANY_EMAILS_" + thing.link_name)}
${error_field("NO_EMAILS_" + thing.link_name)}
${error_field("BAD_EMAILS_emails_" + thing.link_name)}
${error_field("TOO_MANY_EMAILS_emails_" + thing.link_name)}
${error_field("NO_EMAILS_emails_" + thing.link_name)}
</td>
</tr>
<tr>
<th>
<label class="big" for="share_from_${thing.link_name}">
<label for="share_from_${thing.link_name}">
${_("your name")}
</label>&nbsp;
<span class="little gray">
@@ -41,6 +42,44 @@
name="share_from" />
</td>
<td>
${error_field("COMMENT_TOO_LONG_share_from_" + thing.link_name)}
</td>
</tr>
<tr>
<th>
<label for="replyto_${thing.link_name}">
${_("your email")}
</label>&nbsp;
<span class="little gray">
${_("(optional)")}
</span>
</th>
<td>
<input name="replyto" type="text" size="30"
value="${c.user.email if hasattr(c.user, 'email') else ''}"/>
</td>
<td>
${error_field("BAD_EMAILS_replyto_" + thing.link_name)}
${error_field("TOO_MANY_EMAILS_replyto_" + thing.link_name)}
${error_field("NO_EMAILS_replyto_" + thing.link_name)}
</td>
</tr>
<tr>
<th>
<label for="message_${thing.link_name}">
${_("message")}
</label>&nbsp;
<span class="little gray">
${_("(optional)")}
</span>
</th>
<td>
<textarea id="message_${thing.link_name}" name="message" rows="4">
${c.user.name} from http://${g.domain}/ has shared a link with you.
</textarea>
</td>
<td>
${error_field("COMMENT_TOO_LONG_message_" + thing.link_name)}
</td>
</tr>
%if thing.captcha: