<commit_before>import urlparse
import datetime

from formspree.app import DB, redis_store
from formspree import settings, log
from formspree.utils import send_email, unix_time_for_12_months_from_now, next_url
from flask import url_for, render_template
from sqlalchemy.sql.expression import delete
from werkzeug.datastructures import ImmutableMultiDict, ImmutableOrderedMultiDict
from helpers import HASH, HASHIDS_CODEC, MONTHLY_COUNTER_KEY, http_form_to_dict, referrer_to_path

class Form(DB.Model):
    __tablename__ = 'forms'

    id = DB.Column(DB.Integer, primary_key=True)
    hash = DB.Column(DB.String(32), unique=True)
    email = DB.Column(DB.String(120))
    host = DB.Column(DB.String(300))
    confirm_sent = DB.Column(DB.Boolean)
    confirmed = DB.Column(DB.Boolean)
    counter = DB.Column(DB.Integer)
    owner_id = DB.Column(DB.Integer, DB.ForeignKey('users.id'))

    owner = DB.relationship('User') # direct owner, defined by 'owner_id'
                                    # this property is basically useless. use .controllers
    submissions = DB.relationship('Submission',
        backref='form', lazy='dynamic', order_by=lambda: Submission.id.desc())

    '''
    When the form is created by a spontaneous submission, it is added to
    the table with a `host`, an `email` and a `hash` made of these two
    (+ a secret nonce).

    `hash` is UNIQUE because it is used to query these spontaneous forms
    when the form is going to be confirmed and whenever a new submission arrives.

    When a registered user POSTs to /forms, a new form is added to the table
    with an `email` (provided by the user) and an `owner_id`. Later, when this
    form receives its first submission and confirmation, `host` is added, so
    we can ensure that no one will submit to this same form from another host.

    `hash` is never added to these forms, because they could conflict with other
    forms, created by the spontaneous process, with the same email and host. So
    for these forms a different confirmation method is used (see below).
    '''

    STATUS_EMAIL_SENT              = 0
    STATUS_EMAIL_EMPTY             = 1
    STATUS_EMAIL_FAILED            = 2
    STATUS_OVERLIMIT               = 3
    STATUS_REPLYTO_ERROR           = 4

    STATUS_CONFIRMATION_SENT       = 10
    STATUS_CONFIRMATION_DUPLICATED = 11
    STATUS_CONFIRMATION_FAILED     = 12

    def __init__(self, email, host=None, owner=None):
        if host:
            self.hash = HASH(email, host)
        elif owner:
            self.owner_id = owner.id
        else:
            raise Exception('cannot create form without a host and a owner. provide one of these.')
        self.email = email
        self.host = host
        self.confirm_sent = False
        self.confirmed = False
        self.counter = 0

    def __repr__(self):
        return '<Form %s, email=%s, host=%s>' % (self.id, self.email, self.host)

    @property
    def controllers(self):
        from formspree.users.models import User, Email
        by_email = DB.session.query(User) \
            .join(Email, User.id == Email.owner_id) \
            .join(Form, Form.email == Email.address) \
            .filter(Form.id == self.id)
        by_creation = DB.session.query(User) \
            .join(Form, User.id == Form.owner_id) \
            .filter(Form.id == self.id)
        return by_email.union(by_creation)

    @classmethod
    def get_with_hashid(cls, hashid):
        try:
            id = HASHIDS_CODEC.decode(hashid)[0]
            return cls.query.get(id)
        except IndexError:
            return None

    def send(self, submitted_data, referrer):
        '''
        Sends form to user's email.
        Assumes sender's email has been verified.
        '''

        if type(submitted_data) in (ImmutableMultiDict, ImmutableOrderedMultiDict):
            data, keys = http_form_to_dict(submitted_data)
        else:
            data, keys = submitted_data, submitted_data.keys()

        subject = data.get('_subject', 'New submission from %s' % referrer_to_path(referrer))
        reply_to = data.get('_replyto', data.get('email', data.get('Email', None)))
        cc = data.get('_cc', None)
        next = next_url(referrer, data.get('_next'))
        spam = data.get('_gotcha', None)
        format = data.get('_format', None)

        # prevent submitting empty form
        if not any(data.values()):
            return { 'code': Form.STATUS_EMAIL_EMPTY }

        # return a fake success for spam
        if spam:
            return { 'code': Form.STATUS_EMAIL_SENT, 'next': next }

        # increase the monthly counter
        request_date = datetime.datetime.now()
        self.increase_monthly_counter(basedate=request_date)

        # increment the forms counter
        self.counter = Form.counter + 1
        DB.session.add(self)

        # archive the form contents
        sub = Submission(self.id)
        sub.data = data
        DB.session.add(sub)

        # commit changes
        DB.session.commit()

        # delete all archived submissions over the limit
        records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT
        newest = self.submissions.with_entities(Submission.id).limit(records_to_keep)
        DB.engine.execute(
          delete('submissions'). \
          where(Submission.form_id == self.id). \
          where(~Submission.id.in_(newest))
        )

        # check if the forms are over the counter and the user is not upgraded
        overlimit = False
        monthly_counter = self.get_monthly_counter()
        if monthly_counter > settings.MONTHLY_SUBMISSIONS_LIMIT:
            overlimit = True
            if self.controllers:
                for c in self.controllers:
                    if c.upgraded:
                        overlimit = False
                        break

        now = datetime.datetime.utcnow().strftime('%I:%M %p UTC - %d %B %Y')
        if not overlimit:
            text = render_template('email/form.txt', data=data, host=self.host, keys=keys, now=now)
            # check if the user wants a new or old version of the email
            if format is 'text':
                html = render_template('email/text_form.html', data=data, host=self.host, keys=keys, now=now)
            else:
                html = render_template('email/form.html', data=data, host=self.host, keys=keys, now=now)
        else:
            if monthly_counter - settings.MONTHLY_SUBMISSIONS_LIMIT > 25:
                # only send this overlimit notification for the first 25 overlimit emails
                # after that, return an error so the user can know the website owner is not
                # going to read his message.
                return { 'code': Form.STATUS_OVERLIMIT }

            text = render_template('email/overlimit-notification.txt', host=self.host)
            html = render_template('email/overlimit-notification.html', host=self.host)

        result = send_email(to=self.email,
                          subject=subject,
                          text=text,
                          html=html,
                          sender=settings.DEFAULT_SENDER,<commit_msg>Fix because equality wasn't working<commit_after>import urlparse
import datetime

from formspree.app import DB, redis_store
from formspree import settings, log
from formspree.utils import send_email, unix_time_for_12_months_from_now, next_url
from flask import url_for, render_template
from sqlalchemy.sql.expression import delete
from werkzeug.datastructures import ImmutableMultiDict, ImmutableOrderedMultiDict
from helpers import HASH, HASHIDS_CODEC, MONTHLY_COUNTER_KEY, http_form_to_dict, referrer_to_path

class Form(DB.Model):
    __tablename__ = 'forms'

    id = DB.Column(DB.Integer, primary_key=True)
    hash = DB.Column(DB.String(32), unique=True)
    email = DB.Column(DB.String(120))
    host = DB.Column(DB.String(300))
    confirm_sent = DB.Column(DB.Boolean)
    confirmed = DB.Column(DB.Boolean)
    counter = DB.Column(DB.Integer)
    owner_id = DB.Column(DB.Integer, DB.ForeignKey('users.id'))

    owner = DB.relationship('User') # direct owner, defined by 'owner_id'
                                    # this property is basically useless. use .controllers
    submissions = DB.relationship('Submission',
        backref='form', lazy='dynamic', order_by=lambda: Submission.id.desc())

    '''
    When the form is created by a spontaneous submission, it is added to
    the table with a `host`, an `email` and a `hash` made of these two
    (+ a secret nonce).

    `hash` is UNIQUE because it is used to query these spontaneous forms
    when the form is going to be confirmed and whenever a new submission arrives.

    When a registered user POSTs to /forms, a new form is added to the table
    with an `email` (provided by the user) and an `owner_id`. Later, when this
    form receives its first submission and confirmation, `host` is added, so
    we can ensure that no one will submit to this same form from another host.

    `hash` is never added to these forms, because they could conflict with other
    forms, created by the spontaneous process, with the same email and host. So
    for these forms a different confirmation method is used (see below).
    '''

    STATUS_EMAIL_SENT              = 0
    STATUS_EMAIL_EMPTY             = 1
    STATUS_EMAIL_FAILED            = 2
    STATUS_OVERLIMIT               = 3
    STATUS_REPLYTO_ERROR           = 4

    STATUS_CONFIRMATION_SENT       = 10
    STATUS_CONFIRMATION_DUPLICATED = 11
    STATUS_CONFIRMATION_FAILED     = 12

    def __init__(self, email, host=None, owner=None):
        if host:
            self.hash = HASH(email, host)
        elif owner:
            self.owner_id = owner.id
        else:
            raise Exception('cannot create form without a host and a owner. provide one of these.')
        self.email = email
        self.host = host
        self.confirm_sent = False
        self.confirmed = False
        self.counter = 0

    def __repr__(self):
        return '<Form %s, email=%s, host=%s>' % (self.id, self.email, self.host)

    @property
    def controllers(self):
        from formspree.users.models import User, Email
        by_email = DB.session.query(User) \
            .join(Email, User.id == Email.owner_id) \
            .join(Form, Form.email == Email.address) \
            .filter(Form.id == self.id)
        by_creation = DB.session.query(User) \
            .join(Form, User.id == Form.owner_id) \
            .filter(Form.id == self.id)
        return by_email.union(by_creation)

    @classmethod
    def get_with_hashid(cls, hashid):
        try:
            id = HASHIDS_CODEC.decode(hashid)[0]
            return cls.query.get(id)
        except IndexError:
            return None

    def send(self, submitted_data, referrer):
        '''
        Sends form to user's email.
        Assumes sender's email has been verified.
        '''

        if type(submitted_data) in (ImmutableMultiDict, ImmutableOrderedMultiDict):
            data, keys = http_form_to_dict(submitted_data)
        else:
            data, keys = submitted_data, submitted_data.keys()

        subject = data.get('_subject', 'New submission from %s' % referrer_to_path(referrer))
        reply_to = data.get('_replyto', data.get('email', data.get('Email', None)))
        cc = data.get('_cc', None)
        next = next_url(referrer, data.get('_next'))
        spam = data.get('_gotcha', None)
        format = data.get('_format', None)

        # prevent submitting empty form
        if not any(data.values()):
            return { 'code': Form.STATUS_EMAIL_EMPTY }

        # return a fake success for spam
        if spam:
            return { 'code': Form.STATUS_EMAIL_SENT, 'next': next }

        # increase the monthly counter
        request_date = datetime.datetime.now()
        self.increase_monthly_counter(basedate=request_date)

        # increment the forms counter
        self.counter = Form.counter + 1
        DB.session.add(self)

        # archive the form contents
        sub = Submission(self.id)
        sub.data = data
        DB.session.add(sub)

        # commit changes
        DB.session.commit()

        # delete all archived submissions over the limit
        records_to_keep = settings.ARCHIVED_SUBMISSIONS_LIMIT
        newest = self.submissions.with_entities(Submission.id).limit(records_to_keep)
        DB.engine.execute(
          delete('submissions'). \
          where(Submission.form_id == self.id). \
          where(~Submission.id.in_(newest))
        )

        # check if the forms are over the counter and the user is not upgraded
        overlimit = False
        monthly_counter = self.get_monthly_counter()
        if monthly_counter > settings.MONTHLY_SUBMISSIONS_LIMIT:
            overlimit = True
            if self.controllers:
                for c in self.controllers:
                    if c.upgraded:
                        overlimit = False
                        break

        now = datetime.datetime.utcnow().strftime('%I:%M %p UTC - %d %B %Y')
        if not overlimit:
            text = render_template('email/form.txt', data=data, host=self.host, keys=keys, now=now)
            # check if the user wants a new or old version of the email
            if format == 'text':
                html = render_template('email/text_form.html', data=data, host=self.host, keys=keys, now=now)
            else:
                html = render_template('email/form.html', data=data, host=self.host, keys=keys, now=now)
        else:
            if monthly_counter - settings.MONTHLY_SUBMISSIONS_LIMIT > 25:
                # only send this overlimit notification for the first 25 overlimit emails
                # after that, return an error so the user can know the website owner is not
                # going to read his message.
                return { 'code': Form.STATUS_OVERLIMIT }

            text = render_template('email/overlimit-notification.txt', host=self.host)
            html = render_template('email/overlimit-notification.html', host=self.host)

        result = send_email(to=self.email,
                          subject=subject,
                          text=text,
                          html=html,
                          sender=settings.DEFAULT_SENDER,
{'fraction_non_alphanumeric': 0.08944055944055944, 'fraction_numerical': 0.003916083916083916, 'mean_word_length': 2.2598586733530888, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1276291', 'n_tokens_mistral': 4187, 'n_tokens_neox': 4025, 'n_words': 1174}