# The contents of this file are subject to the Common Public Attribution
# License Version 1.0. (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
# License Version 1.1, but Sections 14 and 15 have been added to cover use of
# software over a computer network and provide for limited attribution for the
# Original Developer. In addition, Exhibit A has been modified to be consistent
# with Exhibit B.
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
# the specific language governing rights and limitations under the License.
#
# The Original Code is reddit.
#
# The Original Developer is the Initial Developer.  The Initial Developer of
# the Original Code is reddit Inc.
#
# All portions of the code written by reddit are Copyright (c) 2006-2015 reddit
# Inc. All Rights Reserved.
###############################################################################

from pylons.i18n import _, ungettext
from r2.controllers.reddit_base import (
    base_listing,
    disable_subreddit_css,
    hsts_modify_redirect,
    hsts_eligible,
    pagecache_policy,
    PAGECACHE_POLICY,
    paginated_listing,
    RedditController,
    require_https,
)
from r2 import config
from r2.models import *
from r2.models.recommend import ExploreSettings
from r2.config.extensions import is_api
from r2.lib import recommender
from r2.lib.pages import *
from r2.lib.pages.things import hot_links_by_url_listing
from r2.lib.pages import trafficpages
from r2.lib.menus import *
from r2.lib.admin_utils import check_cheating
from r2.lib.csrf import csrf_exempt
from r2.lib.utils import to36, sanitize_url, title_to_url
from r2.lib.utils import query_string, UrlParser, url_links_builder
from r2.lib.template_helpers import get_domain
from r2.lib.filters import unsafe, _force_unicode, _force_utf8
from r2.lib.emailer import Email
from r2.lib.db.operators import desc
from r2.lib.db import queries
from r2.lib.db.tdb_cassandra import MultiColumnQuery
from r2.lib.strings import strings
from r2.lib.search import (SearchQuery, SubredditSearchQuery, SearchException,
                           InvalidQuery)
from r2.lib.validator import *
from r2.lib import jsontemplates
from r2.lib import sup
import r2.lib.db.thing as thing
from r2.lib.errors import errors, ForbiddenError
from listingcontroller import ListingController
from oauth2 import require_oauth2_scope
from api_docs import api_doc, api_section
from pylons import c, request
from r2.models.token import EmailVerificationToken
from r2.controllers.ipn import generate_blob, validate_blob, GoldException

from operator import attrgetter
import string
import random as rand
import re, socket
import time as time_module
from urllib import quote_plus

class FrontController(RedditController):

    allow_stylesheets = True

    @validate(article=VLink('article'),
              comment=VCommentID('comment'))
    def GET_oldinfo(self, article, type, dest, rest=None, comment=''):
        """Legacy: supporting permalink pages from '06,
           and non-search-engine-friendly links"""
        if not (dest in ('comments','related','details')):
                dest = 'comments'
        if type == 'ancient':
            #this could go in config, but it should never change
            max_link_id = 10000000
            new_id = max_link_id - int(article._id)
            return self.redirect('/info/' + to36(new_id) + '/' + rest)
        if type == 'old':
            if not article.subreddit_slow.can_view(c.user):
                self.abort403()

            new_url = "/%s/%s/%s" % \
                      (dest, article._id36,
                       quote_plus(title_to_url(article.title).encode('utf-8')))
            if not c.default_sr:
                new_url = "/r/%s%s" % (c.site.name, new_url)
            if comment:
                new_url = new_url + "/%s" % comment._id36
            if c.extension:
                new_url = new_url + "/.%s" % c.extension

            new_url = new_url + query_string(request.GET)

            # redirect should be smarter and handle extensions, etc.
            return self.redirect(new_url, code=301)

    @require_oauth2_scope("read")
    @api_doc(api_section.listings, uses_site=True)
    def GET_random(self):
        """The Serendipity button"""
        sort = rand.choice(('new','hot'))
        q = c.site.get_links(sort, 'all')
        if isinstance(q, thing.Query):
            q._limit = g.num_serendipity
            names = [link._fullname for link in q]
        else:
            names = list(q)[:g.num_serendipity]

        rand.shuffle(names)

        def keep_fn(item):
            return item.fresh and item.keep_item(item)

        builder = IDBuilder(names, skip=True, keep_fn=keep_fn, num=1)
        links, first, last, before, after = builder.get_items()

        if links:
            return self.redirect(add_sr("/tb/" + links[0]._id36))
        else:
            return self.redirect(add_sr('/'))

    @disable_subreddit_css()
    @validate(
        VAdmin(),
        thing=VByName('article'),
        oldid36=nop('article'),
        after=nop('after'),
        before=nop('before'),
        count=VCount('count'),
        listing_only=VBoolean('listing_only'),
    )
    def GET_details(self, thing, oldid36, after, before, count, listing_only):
        """The (now deprecated) details page.  Content on this page
        has been subsubmed by the presence of the LinkInfoBar on the
        rightbox, so it is only useful for Admin-only wizardry."""
        if not thing:
            try:
                link = Link._byID36(oldid36)
                return self.redirect('/details/' + link._fullname)
            except (NotFound, ValueError):
                abort(404)

        kw = {
            'count': count,
            'listing_only': listing_only,
        }
        if before:
            kw['after'] = before
            kw['reverse'] = True
        else:
            kw['after'] = after
            kw['reverse'] = False
        c.referrer_policy = "always"
        page = DetailsPage(thing=thing, expand_children=False, **kw)
        if listing_only:
            return page.details.listing.listing().render()
        return page.render()

    @validate(VUser())
    def GET_explore(self):
        settings = ExploreSettings.for_user(c.user)
        recs = recommender.get_recommended_content_for_user(c.user,
                                                            settings,
                                                            record_views=True)
        content = ExploreItemListing(recs, settings)
        return BoringPage(_("explore"),
                          show_sidebar=True,
                          show_chooser=True,
                          page_classes=['explore-page'],
                          content=content).render()
 
    @validate(article=VLink('article'))
    def GET_shirt(self, article):
        if not can_view_link_comments(article):
            abort(403, 'forbidden')
        return self.abort404()

    def _comment_visits(self, article, user, new_visit=None):
        timer = g.stats.get_timer("gold.comment_visits")
        timer.start()

        hc_key = "comment_visits-%s-%s" % (user.name, article._id36)
        old_visits = g.hardcache.get(hc_key, [])

        append = False

        if new_visit is None:
            pass
        elif len(old_visits) == 0:
            append = True
        else:
            last_visit = max(old_visits)
            time_since_last = new_visit - last_visit
            if (time_since_last.days > 0
                or time_since_last.seconds > g.comment_visits_period):
                append = True
            else:
                # They were just here a few seconds ago; consider that
                # the same "visit" as right now
                old_visits.pop()

        if append:
            copy = list(old_visits) # make a copy
            copy.append(new_visit)
            if len(copy) > 10:
                copy.pop(0)
            g.hardcache.set(hc_key, copy, 86400 * 2)

        timer.stop()

        return old_visits


    @csrf_exempt
    @validate(article=VLink('article'),
              comment=VCommentID('comment'),
              context=VInt('context', min=0, max=8),
              sort=VMenu('controller', CommentSortMenu),
              limit=VInt('limit'),
              depth=VInt('depth'))
    def POST_comments(self, article, comment, context, sort, limit, depth):
        # VMenu validator will save the value of sort before we reach this
        # point. Now just redirect to GET mode.
        return self.redirect(request.fullpath + query_string(dict(sort=sort)))

    @require_oauth2_scope("read")
    @validate(article=VLink('article',
                  docs={"article": "ID36 of a link"}),
              comment=VCommentID('comment',
                  docs={"comment": "(optional) ID36 of a comment"}),
              context=VInt('context', min=0, max=8),
              sort=VMenu('controller', CommentSortMenu),
              limit=VInt('limit',
                  docs={"limit": "(optional) an integer"}),
              depth=VInt('depth',
                  docs={"depth": "(optional) an integer"}),
             )
    @api_doc(api_section.listings,
             uri='/comments/{article}',
             uses_site=True,
             extensions=['json', 'xml'])
    def GET_comments(self, article, comment, context, sort, limit, depth):
        """Get the comment tree for a given Link `article`.

        If supplied, `comment` is the ID36 of a comment in the comment tree for
        `article`. This comment will be the (highlighted) focal point of the
        returned view and `context` will be the number of parents shown.

        `depth` is the maximum depth of subtrees in the thread.

        `limit` is the maximum number of comments to return.

        See also: [/api/morechildren](#POST_api_morechildren) and
        [/api/comment](#POST_api_comment).

        """
        if comment and comment.link_id != article._id:
            return self.abort404()

        sr = Subreddit._byID(article.sr_id, True)

        if sr.name == g.takedown_sr:
            request.environ['REDDIT_TAKEDOWN'] = article._fullname
            return self.abort404()

        if not c.default_sr and c.site._id != sr._id:
            return self.abort404()

        if not can_view_link_comments(article):
            abort(403, 'forbidden')

        #check for 304
        self.check_modified(article, 'comments')

        # If there is a focal comment, communicate down to
        # comment_skeleton.html who that will be. Also, skip
        # comment_visits check
        previous_visits = None
        if comment:
            c.focal_comment = comment._id36
        elif (c.user_is_loggedin and c.user.gold and
              c.user.pref_highlight_new_comments):
            previous_visits = self._comment_visits(article, c.user, c.start_time)

        # check if we just came from the submit page
        infotext = None
        if request.GET.get('already_submitted'):
            infotext = strings.already_submitted % article.resubmit_link()

        check_cheating('comments')

        if not c.user.pref_num_comments:
            num = g.num_comments
        elif c.user.gold:
            num = min(c.user.pref_num_comments, g.max_comments_gold)
        else:
            num = min(c.user.pref_num_comments, g.max_comments)

        kw = {}
        # allow depth to be reset (I suspect I'll turn the VInt into a
        # validator on my next pass of .compact)
        if depth is not None and 0 < depth < MAX_RECURSION:
            kw['max_depth'] = depth
        elif c.render_style == "compact":
            kw['max_depth'] = 5

        displayPane = PaneStack()

        # allow the user's total count preferences to be overwritten
        # (think of .embed as the use case together with depth=1)

        if limit and limit > 0:
            num = limit

        if c.user_is_loggedin and c.user.gold:
            if num > g.max_comments_gold:
                displayPane.append(InfoBar(message =
                                           strings.over_comment_limit_gold
                                           % max(0, g.max_comments_gold)))
                num = g.max_comments_gold
        elif num > g.max_comments:
            if limit:
                displayPane.append(InfoBar(message =
                                       strings.over_comment_limit
                                       % dict(max=max(0, g.max_comments),
                                              goldmax=max(0,
                                                   g.max_comments_gold))))
            num = g.max_comments

        # if permalink page, add that message first to the content
        if comment:
            displayPane.append(PermalinkMessage(article.make_permalink_slow()))

        displayPane.append(LinkCommentSep())

        # insert reply box only for logged in user
        if c.user_is_loggedin and can_comment_link(article) and not is_api():
            #no comment box for permalinks
            display = False
            if not comment and article._age < sr.archive_age:
                display = True

            if article.promoted:
                geotargeted, city_target = promote.is_geotargeted_promo(article)
                if geotargeted:
                    displayPane.append(GeotargetNotice(city_target=city_target))

            displayPane.append(UserText(item=article, creating=True,
                                        post_form='comment',
                                        display=display,
                                        cloneable=True))

        if previous_visits:
            displayPane.append(CommentVisitsBox(previous_visits))
            # Used in later "more comments" renderings
            pv_hex = md5(repr(previous_visits)).hexdigest()
            g.cache.set(pv_hex, previous_visits, time=g.comment_visits_period)
            c.previous_visits_hex = pv_hex

        # Used in template_helpers
        c.previous_visits = previous_visits

        if c.site.allows_referrers:
            c.referrer_policy = "always"

        if article.contest_mode:
            if c.user_is_loggedin and sr.is_moderator(c.user):
                sort = "top"
            else:
                sort = "random"

        # finally add the comment listing
        displayPane.append(CommentPane(article, CommentSortMenu.operator(sort),
                                       comment, context, num, **kw))

        subtitle_buttons = []

        if c.focal_comment or context is not None:
            subtitle = None
        elif article.num_comments == 0:
            subtitle = _("no comments (yet)")
        elif article.num_comments <= num:
            subtitle = _("all %d comments") % article.num_comments
        else:
            subtitle = _("top %d comments") % num

            if g.max_comments > num:
                self._add_show_comments_link(subtitle_buttons, article, num,
                                             g.max_comments, gold=False)

            if (c.user_is_loggedin and c.user.gold
                and article.num_comments > g.max_comments):
                self._add_show_comments_link(subtitle_buttons, article, num,
                                             g.max_comments_gold, gold=True)

        res = LinkInfoPage(link=article, comment=comment,
                           content=displayPane,
                           page_classes=['comments-page'],
                           subtitle=subtitle,
                           subtitle_buttons=subtitle_buttons,
                           nav_menus=[CommentSortMenu(default=sort),
                                        LinkCommentsSettings(article)],
                           infotext=infotext).render()
        return res

    def _add_show_comments_link(self, array, article, num, max_comm, gold=False):
        if num == max_comm:
            return
        elif article.num_comments <= max_comm:
            link_text = _("show all %d") % article.num_comments
        else:
            link_text = _("show %d") % max_comm

        limit_param = "?limit=%d" % max_comm

        if gold:
            link_class = "gold"
        else:
            link_class = ""

        more_link = article.make_permalink_slow() + limit_param
        array.append( (link_text, more_link, link_class) )

    @validate(VUser(),
              name=nop('name'))
    def GET_newreddit(self, name):
        """Create a subreddit form"""
        title = _('create a subreddit')
        content=CreateSubreddit(name=name or '')
        res = FormPage(_("create a subreddit"),
                       content=content,
                       ).render()
        return res

    @pagecache_policy(PAGECACHE_POLICY.LOGGEDIN_AND_LOGGEDOUT)
    @require_oauth2_scope("modconfig")
    @api_doc(api_section.moderation, uses_site=True)
    def GET_stylesheet(self):
        """Redirect to the subreddit's stylesheet if one exists.

        See also: [/api/subreddit_stylesheet](#POST_api_subreddit_stylesheet).

        """
        # de-stale the subreddit object so we don't poison downstream caches
        if not isinstance(c.site, FakeSubreddit):
            c.site = Subreddit._byID(c.site._id, data=True, stale=False)

        url = Reddit.get_subreddit_stylesheet_url(c.site)
        if url:
            return self.redirect(url)
        else:
            self.abort404()

    def _make_moderationlog(self, srs, num, after, reverse, count, mod=None, action=None):
        query = Subreddit.get_modactions(srs, mod=mod, action=action)
        builder = ModActionBuilder(
            query, num=num, after=after, count=count, reverse=reverse,
            wrap=default_thing_wrapper())
        listing = ModActionListing(builder)
        pane = listing.listing()
        return pane

    modname_splitter = re.compile('[ ,]+')

    @require_oauth2_scope("modlog")
    @disable_subreddit_css()
    @paginated_listing(max_page_size=500, backend='cassandra')
    @validate(
        mod=nop('mod', docs={"mod": "(optional) a moderator filter"}),
        action=VOneOf('type', ModAction.actions),
    )
    @api_doc(api_section.moderation, uses_site=True,
             uri="/about/log", extensions=["json", "xml"])
    def GET_moderationlog(self, num, after, reverse, count, mod, action):
        """Get a list of recent moderation actions.

        Moderator actions taken within a subreddit are logged. This listing is
        a view of that log with various filters to aid in analyzing the
        information.

        The optional `mod` parameter can be a comma-delimited list of moderator
        names to restrict the results to, or the string `a` to restrict the
        results to admin actions taken within the subreddit.

        The `type` parameter is optional and if sent limits the log entries
        returned to only those of the type specified.

        """
        if not c.user_is_loggedin or not (c.user_is_admin or
                                          c.site.is_moderator(c.user)):
            return self.abort404()

        if mod:
            if mod == 'a':
                modnames = g.admins
            else:
                modnames = self.modname_splitter.split(mod)
            mod = []
            for name in modnames:
                try:
                    mod.append(Account._by_name(name, allow_deleted=True))
                except NotFound:
                    continue
            mod = mod or None

        if isinstance(c.site, (MultiReddit, ModSR)):
            srs = Subreddit._byID(c.site.sr_ids, return_dict=False)

            # grab all moderators
            mod_ids = set(Subreddit.get_all_mod_ids(srs))
            mods = Account._byID(mod_ids, data=True)

            pane = self._make_moderationlog(srs, num, after, reverse, count,
                                            mod=mod, action=action)
        elif isinstance(c.site, FakeSubreddit):
            return self.abort404()
        else:
            mod_ids = c.site.moderators
            mods = Account._byID(mod_ids, data=True)

            pane = self._make_moderationlog(c.site, num, after, reverse, count,
                                            mod=mod, action=action)

        panes = PaneStack()
        panes.append(pane)

        action_buttons = [QueryButton(_('all'), None, query_param='type',
                                      css_class='primary')]
        for a in ModAction.actions:
            button = QueryButton(ModAction._menu[a], a, query_param='type')
            action_buttons.append(button)

        mod_buttons = [QueryButton(_('all'), None, query_param='mod',
                                   css_class='primary')]
        for mod_id in mod_ids:
            mod = mods[mod_id]
            mod_buttons.append(QueryButton(mod.name, mod.name,
                                           query_param='mod'))
        mod_buttons.append(QueryButton(_('admins*'), 'a', query_param='mod'))
        base_path = request.path
        menus = [NavMenu(action_buttons, base_path=base_path,
                         title=_('filter by action'), type='lightdrop', css_class='modaction-drop'),
                NavMenu(mod_buttons, base_path=base_path,
                        title=_('filter by moderator'), type='lightdrop')]
        extension_handling = "private" if c.user.pref_private_feeds else False
        return EditReddit(content=panes,
                          nav_menus=menus,
                          location="log",
                          extension_handling=extension_handling).render()

    def _make_spamlisting(self, location, only, num, after, reverse, count):
        include_links, include_comments = True, True
        if only == 'links':
            include_comments = False
        elif only == 'comments':
            include_links = False

        if location == 'reports':
            query = c.site.get_reported(include_links=include_links,
                                        include_comments=include_comments)
        elif location == 'spam':
            query = c.site.get_spam(include_links=include_links,
                                    include_comments=include_comments)
        elif location == 'modqueue':
            query = c.site.get_modqueue(include_links=include_links,
                                        include_comments=include_comments)
        elif location == 'unmoderated':
            query = c.site.get_unmoderated()
        elif location == 'edited':
            query = c.site.get_edited(include_links=include_links,
                                      include_comments=include_comments)
        else:
            raise ValueError

        if isinstance(query, thing.Query):
            builder_cls = QueryBuilder
        elif isinstance (query, list):
            builder_cls = QueryBuilder
        else:
            builder_cls = IDBuilder

        def keep_fn(x):
            # no need to bother mods with banned users, or deleted content
            if x._deleted:
                return False
            if getattr(x,'author',None) == c.user and c.user._spam:
                return False

            if location == "reports":
                return x.reported > 0 and not x._spam
            elif location == "spam":
                return x._spam
            elif location == "modqueue":
                if x.reported > 0 and not x._spam:
                    return True # reported but not banned
                if x.author._spam and x.subreddit.exclude_banned_modqueue:
                    # banned user, don't show if subreddit pref excludes
                    return False

                verdict = getattr(x, "verdict", None)
                if verdict is None:
                    return True # anything without a verdict
                if x._spam and verdict != 'mod-removed':
                    return True # spam, unless banned by a moderator
                return False
            elif location == "unmoderated":
                # banned user, don't show if subreddit pref excludes
                if x.author._spam and x.subreddit.exclude_banned_modqueue:
                    return False
                return not getattr(x, 'verdict', None)
            elif location == "edited":
                return bool(getattr(x, "editted", False))
            else:
                raise ValueError

        builder = builder_cls(query,
                              skip=True,
                              num=num, after=after,
                              keep_fn=keep_fn,
                              count=count, reverse=reverse,
                              wrap=ListingController.builder_wrapper,
                              spam_listing=True)
        listing = LinkListing(builder)
        pane = listing.listing()

        # Indicate that the comment tree wasn't built for comments
        for i in pane.things:
            if hasattr(i, 'body'):
                i.child = None

        return pane

    def _edit_normal_reddit(self, location, created):
        if (location == 'edit' and
            c.user_is_loggedin and
            (c.user_is_admin or c.site.is_moderator_with_perms(c.user, 'config'))):
            pane = PaneStack()
            if created == 'true':
                pane.append(InfoBar(message=strings.sr_created))
            c.allow_styles = True
            c.site = Subreddit._byID(c.site._id, data=True, stale=False)
            pane.append(CreateSubreddit(site=c.site))
        elif (location == 'stylesheet'
              and c.site.can_change_stylesheet(c.user)
              and not g.css_killswitch):
            stylesheet_contents = c.site.fetch_stylesheet_source()
            c.allow_styles = True
            pane = SubredditStylesheet(site=c.site,
                                       stylesheet_contents=stylesheet_contents)
        elif (location == 'stylesheet'
              and c.site.can_view(c.user)
              and not g.css_killswitch):
            stylesheet = c.site.fetch_stylesheet_source()
            pane = SubredditStylesheetSource(stylesheet_contents=stylesheet)
        elif (location == 'traffic' and
              (c.site.public_traffic or
               (c.user_is_loggedin and
                (c.site.is_moderator(c.user) or c.user.employee)))):
            pane = trafficpages.SubredditTraffic()
        elif (location == "about") and is_api():
            return self.redirect(add_sr('about.json'), code=301)
        else:
            return self.abort404()

        return EditReddit(content=pane,
                          location=location,
                          extension_handling=False).render()

    @require_oauth2_scope("read")
    @base_listing
    @disable_subreddit_css()
    @validate(VSrModerator(perms='posts'),
              location=nop('location'),
              only=VOneOf('only', ('links', 'comments')))
    @api_doc(
        api_section.moderation,
        uses_site=True,
        uri='/about/{location}',
        uri_variants=['/about/' + loc for loc in
                      ('reports', 'spam', 'modqueue', 'unmoderated', 'edited')],
    )
    def GET_spamlisting(self, location, only, num, after, reverse, count):
        """Return a listing of posts relevant to moderators.

        * reports: Things that have been reported.
        * spam: Things that have been marked as spam or otherwise removed.
        * modqueue: Things requiring moderator review, such as reported things
            and items caught by the spam filter.
        * unmoderated: Things that have yet to be approved/removed by a mod.
        * edited: Things that have been edited recently.

        Requires the "posts" moderator permission for the subreddit.

        """
        c.allow_styles = True
        c.profilepage = True
        panes = PaneStack()

        # We clone and modify this when a user clicks 'reply' on a comment.
        replyBox = UserText(item=None, display=False, cloneable=True,
                            creating=True, post_form='comment')
        panes.append(replyBox)

        spamlisting = self._make_spamlisting(location, only, num, after,
                                             reverse, count)
        panes.append(spamlisting)

        extension_handling = "private" if c.user.pref_private_feeds else False

        if location in ('reports', 'spam', 'modqueue', 'edited'):
            buttons = [
                QueryButton(_('links and comments'), None, query_param='only'),
                QueryButton(_('links'), 'links', query_param='only'),
                QueryButton(_('comments'), 'comments', query_param='only'),
            ]
            menus = [NavMenu(buttons, base_path=request.path, title=_('show'),
                             type='lightdrop')]
        else:
            menus = None
        return EditReddit(content=panes,
                          location=location,
                          nav_menus=menus,
                          extension_handling=extension_handling).render()

    @base_listing
    @disable_subreddit_css()
    @validate(VSrModerator(perms='flair'),
              name=nop('name'))
    def GET_flairlisting(self, num, after, reverse, count, name):
        user = None
        if name:
            try:
                user = Account._by_name(name)
            except NotFound:
                c.errors.add(errors.USER_DOESNT_EXIST, field='name')

        c.allow_styles = True
        pane = FlairPane(num, after, reverse, name, user)
        return EditReddit(content=pane, location='flair').render()

    @require_oauth2_scope("modconfig")
    @disable_subreddit_css()
    @validate(location=nop('location'),
              created=VOneOf('created', ('true','false'),
                             default='false'))
    @api_doc(api_section.subreddits, uri="/r/{subreddit}/about/edit",
             extensions=["json"])
    def GET_editreddit(self, location, created):
        """Get the current settings of a subreddit.

        In the API, this returns the current settings of the subreddit as used
        by [/api/site_admin](#POST_api_site_admin).  On the HTML site, it will
        display a form for editing the subreddit.

        """
        c.profilepage = True
        if isinstance(c.site, FakeSubreddit):
            return self.abort404()
        else:
            return self._edit_normal_reddit(location, created)

    @require_oauth2_scope("read")
    @api_doc(api_section.subreddits, uri='/r/{subreddit}/about', extensions=['json'])
    def GET_about(self):
        """Return information about the subreddit.

        Data includes the subscriber count, description, and header image."""
        if not is_api() or isinstance(c.site, FakeSubreddit):
            return self.abort404()
        return Reddit(content=Wrapped(c.site)).render()

    @require_oauth2_scope("read")
    def GET_sidebar(self):
        usertext = UserText(c.site, c.site.description)
        return Reddit(content=usertext).render()

    @require_oauth2_scope("read")
    def GET_sticky(self):
        if c.site.sticky_fullname:
            sticky = Link._by_fullname(c.site.sticky_fullname, data=True)
            self.redirect(sticky.make_permalink_slow())
        else:
            abort(404)

    def GET_awards(self):
        """The awards page."""
        return BoringPage(_("awards"), content=UserAwards()).render()

    # filter for removing punctuation which could be interpreted as search syntax
    related_replace_regex = re.compile(r'[?\\&|!{}+~^()"\':*-]+')
    related_replace_with = ' '

    @base_listing
    @require_oauth2_scope("read")
    @validate(article=VLink('article'))
    def GET_related(self, num, article, after, reverse, count):
        """Related page: performs a search using title of article as
        the search query.

        """
        if not can_view_link_comments(article):
            abort(403, 'forbidden')

        query = self.related_replace_regex.sub(self.related_replace_with,
                                               article.title)
        query = _force_unicode(query)
        query = query[:1024]
        query = u"|".join(query.split())
        query = u"title:'%s'" % query
        rel_range = timedelta(days=3)
        start = int(time_module.mktime((article._date - rel_range).utctimetuple()))
        end = int(time_module.mktime((article._date + rel_range).utctimetuple()))
        nsfw = u"nsfw:0" if not (article.over_18 or article._nsfw.findall(article.title)) else u""
        query = u"(and %s timestamp:%s..%s %s)" % (query, start, end, nsfw)
        q = SearchQuery(query, raw_sort="-text_relevance",
                        syntax="cloudsearch")
        pane = self._search(q, num=num, after=after, reverse=reverse,
                            count=count)[2]

        return LinkInfoPage(link=article, content=pane,
                            page_classes=['related-page'],
                            subtitle=_('related')).render()

    @base_listing
    @require_oauth2_scope("read")
    @validate(article=VLink('article'))
    def GET_duplicates(self, article, num, after, reverse, count):
        if not can_view_link_comments(article):
            abort(403, 'forbidden')

        builder = url_links_builder(article.url, exclude=article._fullname,
                                    num=num, after=after, reverse=reverse,
                                    count=count)
        if after and not builder.valid_after(after):
            g.stats.event_count("listing.invalid_after", "duplicates")
            self.abort403()
        num_duplicates = len(builder.get_items()[0])
        listing = LinkListing(builder).listing()

        res = LinkInfoPage(link=article,
                           comment=None,
                           num_duplicates=num_duplicates,
                           content=listing,
                           page_classes=['other-discussions-page'],
                           subtitle=_('other discussions')).render()
        return res


    @base_listing
    @require_oauth2_scope("read")
    @validate(query=nop('q', docs={"q": "a search query"}))
    @api_doc(api_section.subreddits, uri='/subreddits/search', extensions=['json', 'xml'])
    def GET_search_reddits(self, query, reverse, after, count, num):
        """Search subreddits by title and description."""
        q = SubredditSearchQuery(query)

        results, etime, spane = self._search(q, num=num, reverse=reverse,
                                             after=after, count=count,
                                             skip_deleted_authors=False)

        res = SubredditsPage(content=spane,
                             prev_search=query,
                             elapsed_time=etime,
                             # update if we ever add sorts
                             search_params={},
                             title=_("search results"),
                             simple=True).render()
        return res

    search_help_page = "/wiki/search"
    verify_langs_regex = re.compile(r"\A[a-z][a-z](,[a-z][a-z])*\Z")
    @base_listing
    @require_oauth2_scope("read")
    @validate(query=VLength('q', max_length=512),
              sort=VMenu('sort', SearchSortMenu, remember=False),
              recent=VMenu('t', TimeMenu, remember=False),
              restrict_sr=VBoolean('restrict_sr', default=False),
              syntax=VOneOf('syntax', options=SearchQuery.known_syntaxes))
    @api_doc(api_section.search, extensions=['json', 'xml'], uses_site=True)
    def GET_search(self, query, num, reverse, after, count, sort, recent,
                   restrict_sr, syntax):
        """Search links page."""
        if query and '.' in query:
            url = sanitize_url(query, require_scheme=True)
            if url:
                return self.redirect("/submit" + query_string({'url':url}))

        if not restrict_sr:
            site = DefaultSR()
        else:
            site = c.site

        if not syntax:
            syntax = SearchQuery.default_syntax

        try:
            cleanup_message = None
            try:
                q = SearchQuery(query, site, sort,
                                recent=recent, syntax=syntax)
                results, etime, spane = self._search(q, num=num, after=after,
                                                     reverse=reverse,
                                                     count=count)
            except InvalidQuery:
                # Clean the search of characters that might be causing the
                # InvalidQuery exception. If the cleaned search boils down
                # to an empty string, the search code is expected to bail
                # out early with an empty result set.
                cleaned = re.sub("[^\w\s]+", " ", query)
                cleaned = cleaned.lower().strip()

                q = SearchQuery(cleaned, site, sort, recent=recent)
                results, etime, spane = self._search(q, num=num,
                                                     after=after,
                                                     reverse=reverse,
                                                     count=count)
                if cleaned:
                    cleanup_message = strings.invalid_search_query % {
                                                        "clean_query": cleaned
                                                                      }
                    cleanup_message += " "
                    cleanup_message += strings.search_help % {
                                          "search_help": self.search_help_page
                                                              }
                else:
                    cleanup_message = strings.completely_invalid_search_query

            check_cheating("search")
            res = SearchPage(_('search results'), query, etime,
                             content=spane,
                             nav_menus=[SearchSortMenu(default=sort),
                                        TimeMenu(default=recent)],
                             search_params=dict(sort=sort, t=recent),
                             infotext=cleanup_message,
                             simple=False, site=c.site,
                             restrict_sr=restrict_sr,
                             syntax=syntax,
                             converted_data=q.converted_data,
                             facets=results.subreddit_facets,
                             sort=sort,
                             recent=recent,
                             ).render()

            return res
        except SearchException + (socket.error,) as e:
            return self.search_fail(e)

    def _search(self, query_obj, num, after, reverse, count=0,
                skip_deleted_authors=True):
        """Helper function for interfacing with search.  Basically a
           thin wrapper for SearchBuilder."""

        builder = SearchBuilder(query_obj,
                                after=after, num=num, reverse=reverse,
                                count=count,
                                wrap=ListingController.builder_wrapper,
                                skip_deleted_authors=skip_deleted_authors)
        if after and not builder.valid_after(after):
            g.stats.event_count("listing.invalid_after", "search")
            self.abort403()

        listing = LinkListing(builder, show_nums=True)

        # have to do it in two steps since total_num and timing are only
        # computed after fetch_more
        try:
            res = listing.listing()
        except SearchException + (socket.error,) as e:
            return self.search_fail(e)
        timing = time_module.time() - builder.start_time

        return builder.results, timing, res

    @validate(VAdmin(),
              comment=VCommentByID('comment_id'))
    def GET_comment_by_id(self, comment):
        href = comment.make_permalink_slow(context=5, anchor=True)
        return self.redirect(href)

    @validate(url=VRequired('url', None),
              title=VRequired('title', None),
              text=VRequired('text', None),
              selftext=VRequired('selftext', None),
              then=VOneOf('then', ('tb','comments'), default='comments'))
    def GET_submit(self, url, title, text, selftext, then):
        """Submit form."""
        resubmit = request.GET.get('resubmit')
        url = sanitize_url(url)

        if url and not resubmit:
            # check to see if the url has already been submitted
            listing = hot_links_by_url_listing(url, sr=c.site)
            links = listing.things

            if links and len(links) == 1:
                return self.redirect(links[0].already_submitted_link)
            elif links:
                infotext = (strings.multiple_submitted
                            % links[0].resubmit_link())
                res = BoringPage(_("seen it"),
                                 content=listing,
                                 infotext=infotext).render()
                return res

        if not c.user_is_loggedin:
            raise UserRequiredException

        if not (c.default_sr or c.site.can_submit(c.user)):
            abort(403, "forbidden")

        captcha = Captcha() if c.user.needs_captcha() else None

        extra_subreddits = []
        if isinstance(c.site, MultiReddit):
            extra_subreddits.append((
                _('%s subreddits') % c.site.name,
                c.site.srs
            ))

        newlink = NewLink(
            url=url or '',
            title=title or '',
            text=text or '',
            selftext=selftext or '',
            captcha=captcha,
            resubmit=resubmit,
            default_sr=c.site if not c.default_sr else None,
            extra_subreddits=extra_subreddits,
            show_link=c.default_sr or c.site.can_submit_link(c.user),
            show_self=((c.default_sr or c.site.can_submit_text(c.user))
                      and not request.GET.get('no_self')),
            then=then,
        )

        return FormPage(_("submit"),
                        show_sidebar=True,
                        page_classes=['submit-page'],
                        content=newlink).render()

    def GET_frame(self):
        """used for cname support.  makes a frame and
        puts the proper url as the frame source"""
        sub_domain = request.environ.get('sub_domain')
        original_path = request.environ.get('original_path')
        sr = Subreddit._by_domain(sub_domain)
        return Cnameframe(original_path, sr, sub_domain).render()


    def GET_framebuster(self, what=None, blah=None):
        """
        renders the contents of the iframe which, on a cname, checks
        if the user is currently logged into reddit.

        if this page is hit from the primary domain, redirects to the
        cnamed domain version of the site.  If the user is logged in,
        this cnamed version will drop a boolean session cookie on that
        domain so that subsequent page reloads will be caught in
        middleware and a frame will be inserted around the content.

        If the user is not logged in, previous session cookies will be
        emptied so that subsequent refreshes will not be rendered in
        that pesky frame.
        """
        if not c.site.domain:
            return ""
        elif c.cname:
            return FrameBuster(login=(what == "login")).render()
        else:
            path = "/framebuster/"
            if c.user_is_loggedin:
                path += "login/"
            u = UrlParser(path + str(random.random()))
            u.mk_cname(require_frame=False, subreddit=c.site,
                       port=request.port)
            return self.redirect(u.unparse())
        # the user is not logged in or there is no cname.
        return FrameBuster(login=False).render()

    def GET_catchall(self):
        return self.abort404()

    @validate(period=VInt('seconds',
                          min=sup.MIN_PERIOD,
                          max=sup.MAX_PERIOD,
                          default=sup.MIN_PERIOD))
    def GET_sup(self, period):
        #dont cache this, it's memoized elsewhere
        c.used_cache = True
        sup.set_expires_header()

        if c.extension == 'json':
            return sup.sup_json(period)
        else:
            return self.abort404()


    @require_oauth2_scope("modtraffic")
    @validate(VSponsor('link'),
              link=VLink('link'),
              campaign=VPromoCampaign('campaign'),
              before=VDate('before', format='%Y%m%d%H'),
              after=VDate('after', format='%Y%m%d%H'))
    def GET_traffic(self, link, campaign, before, after):
        if link and campaign and link._id != campaign.link_id:
            return self.abort404()

        if c.render_style == 'csv':
            return trafficpages.PromotedLinkTraffic.as_csv(campaign or link)

        content = trafficpages.PromotedLinkTraffic(link, campaign, before,
                                                   after)
        return LinkInfoPage(link=link,
                            page_classes=["promoted-traffic"],
                            show_sidebar=False, comment=None,
                            show_promote_button=True, content=content).render()

    @validate(VEmployee())
    def GET_site_traffic(self):
        return trafficpages.SitewideTrafficPage().render()

    @validate(VEmployee())
    def GET_lang_traffic(self, langcode):
        return trafficpages.LanguageTrafficPage(langcode).render()

    @validate(VEmployee())
    def GET_advert_traffic(self, code):
        return trafficpages.AdvertTrafficPage(code).render()

    @validate(VEmployee())
    def GET_subreddit_traffic_report(self):
        content = trafficpages.SubredditTrafficReport()

        if c.render_style == 'csv':
            return content.as_csv()
        return trafficpages.TrafficPage(content=content).render()

    @validate(VUser())
    def GET_account_activity(self):
        return AccountActivityPage().render()

    def GET_contact_us(self):
        return BoringPage(_("contact us"), show_sidebar=False,
                          content=ContactUs(), page_classes=["contact-us-page"]
                          ).render()

    def GET_rules(self):
        return BoringPage(_("rules of reddit"), show_sidebar=False,
                          content=RulesPage(), page_classes=["rulespage-body"]
                          ).render()

    @validate(vendor=VOneOf("v", ("claimed-gold", "claimed-creddits",
                                  "spent-creddits", "paypal", "coinbase",
                                  "stripe"),
                            default="claimed-gold"))
    def GET_goldthanks(self, vendor):
        vendor_url = None
        lounge_md = None

        if vendor == "claimed-gold":
            claim_msg = _("Claimed! Enjoy your reddit gold membership.")
            if g.lounge_reddit:
                lounge_md = strings.lounge_msg
        elif vendor == "claimed-creddits":
            claim_msg = _("Your gold creddits have been claimed! Now go to "
                          "someone's userpage and give them a present!")
        elif vendor == "spent-creddits":
            claim_msg = _("Thanks for buying reddit gold! Your transaction "
                          "has been completed.")
        elif vendor == "paypal":
            claim_msg = _("Thanks for buying reddit gold! Your transaction "
                          "has been completed and emailed to you. You can "
                          "check the details by signing into your account "
                          "at:")
            vendor_url = "https://www.paypal.com/us"
        elif vendor in {"coinbase", "stripe"}:  # Pending vendors
            claim_msg = _("Thanks for buying reddit gold! Your transaction is "
                          "being processed. If you have any questions please "
                          "email us at %(gold_email)s")
            claim_msg = claim_msg % {'gold_email': g.goldthanks_email}
        else:
            abort(404)

        return BoringPage(_("thanks"), show_sidebar=False,
                          content=GoldThanks(claim_msg=claim_msg,
                                             vendor_url=vendor_url,
                                             lounge_md=lounge_md)).render()

    @validate(VUser(),
              token=VOneTimeToken(AwardClaimToken, "code"))
    def GET_confirm_award_claim(self, token):
        if not token:
            abort(403)

        award = Award._by_fullname(token.awardfullname)
        trophy = FakeTrophy(c.user, award, token.description, token.url)
        content = ConfirmAwardClaim(trophy=trophy, user=c.user.name,
                                    token=token)
        return BoringPage(_("claim this award?"), content=content).render()

    @validate(VUser(),
              VModhash(),
              token=VOneTimeToken(AwardClaimToken, "code"))
    def POST_claim_award(self, token):
        if not token:
            abort(403)

        token.consume()

        award = Award._by_fullname(token.awardfullname)
        trophy, preexisting = Trophy.claim(c.user, token.uid, award,
                                           token.description, token.url)
        redirect = '/awards/received?trophy=' + trophy._id36
        if preexisting:
            redirect += '&duplicate=true'
        self.redirect(redirect)

    @validate(trophy=VTrophy('trophy'),
              preexisting=VBoolean('duplicate'))
    def GET_received_award(self, trophy, preexisting):
        content = AwardReceived(trophy=trophy, preexisting=preexisting)
        return BoringPage(_("award claim"), content=content).render()

    def GET_gilding(self):
        return BoringPage(
            _("gilding"),
            show_sidebar=False,
            content=Gilding(),
            page_classes=["gold-page", "gilding"],
        ).render()

    @csrf_exempt
    @validate(dest=VDestination(default='/'))
    def _modify_hsts_grant(self, dest):
        """Endpoint subdomains can redirect through to update HSTS grants."""
        from r2.lib.base import abort
        require_https()
        if request.host != g.domain:
            abort(ForbiddenError(errors.WRONG_DOMAIN))

        # We can't send the user back to http: if they're forcing HTTPS
        if c.user.https_forced:
            dest_parsed = UrlParser(dest)
            dest_parsed.scheme = "https"
            dest = dest_parsed.unparse()

        return self.redirect(dest, code=307)

    POST_modify_hsts_grant = _modify_hsts_grant
    GET_modify_hsts_grant = _modify_hsts_grant
    DELETE_modify_hsts_grant = _modify_hsts_grant
    PUT_modify_hsts_grant = _modify_hsts_grant


class FormsController(RedditController):

    def GET_password(self):
        """The 'what is my password' page"""
        return BoringPage(_("password"), content=Password()).render()

    @validate(VUser(),
              dest=VDestination(),
              reason=nop('reason'))
    def GET_verify(self, dest, reason):
        if c.user.email_verified:
            content = InfoBar(message=strings.email_verified)
            if dest:
                return self.redirect(dest)
        else:
            if reason == "submit":
                infomsg = strings.verify_email_submit
            else:
                infomsg = strings.verify_email

            content = PaneStack(
                [InfoBar(message=infomsg),
                 PrefUpdate(email=True, verify=True,
                            password=False, dest=dest)])
        return BoringPage(_("verify email"), content=content).render()

    @validate(VUser(),
              token=VOneTimeToken(EmailVerificationToken, "key"),
              dest=VDestination(default="/prefs/update?verified=true"))
    def GET_verify_email(self, token, dest):
        fail_msg = None
        if token and token.user_id != c.user._fullname:
            fail_msg = strings.email_verify_wrong_user
        elif c.user.email_verified:
            # they've already verified.
            if token:
                # consume and ignore this token (if not already consumed).
                token.consume()
            return self.redirect(dest)
        elif token and token.valid_for_user(c.user):
            # successful verification!
            token.consume()
            c.user.email_verified = True
            c.user._commit()
            Award.give_if_needed("verified_email", c.user)
            return self.redirect(dest)

        # failure. let 'em know.
        content = PaneStack(
            [InfoBar(message=fail_msg or strings.email_verify_failed),
             PrefUpdate(email=True,
                        verify=True,
                        password=False)])
        return BoringPage(_("verify email"), content=content).render()

    @validate(token=VOneTimeToken(PasswordResetToken, "key"),
              key=nop("key"))
    def GET_resetpassword(self, token, key):
        """page hit once a user has been sent a password reset email
        to verify their identity before allowing them to update their
        password."""

        done = False
        if not key and request.referer:
            referer_path = request.referer.split(g.domain)[-1]
            done = referer_path.startswith(request.fullpath)
        elif not token:
            return self.redirect("/password?expired=true")

        token_user = Account._by_fullname(token.user_id, data=True)

        return BoringPage(
            _("reset password"),
            content=ResetPassword(
                key=key,
                done=done,
                username=token_user.name,
            )
        ).render()

    @disable_subreddit_css()
    @validate(VUser(),
              location=nop("location"),
              verified=VBoolean("verified"))
    def GET_prefs(self, location='', verified=False):
        """Preference page"""
        content = None
        infotext = None
        if not location or location == 'options':
            content = PrefOptions(done=request.GET.get('done'))
        elif location == 'update':
            if verified:
                infotext = strings.email_verified
            content = PrefUpdate()
        elif location == 'apps':
            content = PrefApps(my_apps=OAuth2Client._by_user_grouped(c.user),
                               developed_apps=OAuth2Client._by_developer(c.user))
        elif location == 'feeds' and c.user.pref_private_feeds:
            content = PrefFeeds()
        elif location == 'delete':
            content = PrefDelete()
        elif location == 'security':
            content = PrefSecurity()
        else:
            return self.abort404()

        return PrefsPage(content=content, infotext=infotext).render()


    @validate(dest=VDestination())
    def GET_login(self, dest):
        """The /login form.  No link to this page exists any more on
        the site (all actions invoking it now go through the login
        cover).  However, this page is still used for logging the user
        in during submission or voting from the bookmarklets."""

        if (c.user_is_loggedin and
            not request.environ.get('extension') == 'embed'):
            return self.redirect(dest)
        return LoginPage(dest=dest).render()


    @validate(dest=VDestination())
    def GET_register(self, dest):
        if (c.user_is_loggedin and
            not request.environ.get('extension') == 'embed'):
            return self.redirect(dest)
        return RegisterPage(dest=dest).render()

    @validate(VUser(),
              VModhash(),
              dest=VDestination())
    def GET_logout(self, dest):
        return self.redirect(dest)

    @validate(VUser(),
              VModhash(),
              dest=VDestination())
    def POST_logout(self, dest):
        """wipe login cookie and redirect to referer."""
        if hsts_eligible():
            dest = hsts_modify_redirect(dest)
        self.logout()
        return self.redirect(dest)


    @validate(VUser(),
              dest=VDestination())
    def GET_adminon(self, dest):
        """Enable admin interaction with site"""
        #check like this because c.user_is_admin is still false
        if not c.user.name in g.admins:
            return self.abort404()

        return AdminModeInterstitial(dest=dest).render()

    @validate(VAdmin(),
              dest=VDestination())
    def GET_adminoff(self, dest):
        """disable admin interaction with site."""
        if not c.user.name in g.admins:
            return self.abort404()
        self.disable_admin_mode(c.user)
        return self.redirect(dest)

    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()
        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):
        """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)

    @validate(dest=VDestination("dest"))
    def GET_try_compact(self, dest):
        c.render_style = "compact"
        return TryCompact(dest=dest).render()

    @validate(VUser(),
              secret=VPrintable("secret", 50))
    def GET_claim(self, secret):
        """The page to claim reddit gold trophies"""
        return BoringPage(_("thanks"), content=Thanks(secret)).render()

    @validate(VUser(),
              passthrough=nop('passthrough'))
    def GET_creditgild(self, passthrough):
        """Used only for setting up credit card payments for gilding."""
        try:
            payment_blob = validate_blob(passthrough)
        except GoldException:
            self.abort404()

        if c.user != payment_blob['buyer']:
            self.abort404()

        if not payment_blob['goldtype'] == 'gift':
            self.abort404()

        recipient = payment_blob['recipient']
        thing = payment_blob.get('thing')
        if not thing:
            thing = payment_blob['comment']
        if (not thing or
            thing._deleted or
            not thing.subreddit_slow.can_view(c.user)):
            self.abort404()

        if isinstance(thing, Comment):
            summary = strings.gold_summary_gilding_page_comment
        else:
            summary = strings.gold_summary_gilding_page_link
        summary = summary % {'recipient': recipient.name}
        months = 1
        price = g.gold_month_price * months

        if isinstance(thing, Comment):
            desc = thing.body
        else:
            desc = thing.markdown_link_slow()

        content = CreditGild(
            summary=summary,
            price=price,
            months=months,
            stripe_key=g.secrets['stripe_public_key'],
            passthrough=passthrough,
            description=desc,
            period=None,
        )

        return BoringPage(_("reddit gold"),
                          show_sidebar=False,
                          content=content).render()

    @validate(is_payment=VBoolean("is_payment"),
              goldtype=VOneOf("goldtype",
                              ("autorenew", "onetime", "creddits", "gift",
                               "code")),
              period=VOneOf("period", ("monthly", "yearly")),
              months=VInt("months"),
              num_creddits=VInt("num_creddits"),
              # variables below are just for gifts
              signed=VBoolean("signed", default=True),
              recipient=VExistingUname("recipient", default=None),
              thing=VByName("thing"),
              giftmessage=VLength("giftmessage", 10000),
              email=ValidEmail("email"),
              edit=VBoolean("edit", default=False))
    def GET_gold(self, is_payment, goldtype, period, months, num_creddits,
                 signed, recipient, giftmessage, thing, email, edit):

        if thing:
            thing_sr = Subreddit._byID(thing.sr_id, data=True)
            if (thing._deleted or
                    thing._spam or
                    not thing_sr.can_view(c.user) or
                    not thing_sr.allow_gilding):
                thing = None

        start_over = False

        if edit:
            start_over = True

        if not c.user_is_loggedin:
            if goldtype != "code":
                start_over = True
            elif months is None or months < 1:
                start_over = True
            elif not email:
                start_over = True
        elif goldtype == "autorenew":
            if period is None:
                start_over = True
            elif c.user.has_gold_subscription:
                return self.redirect("/gold/subscription")
        elif goldtype in ("onetime", "code"):
            if months is None or months < 1:
                start_over = True
        elif goldtype == "creddits":
            if num_creddits is None or num_creddits < 1:
                start_over = True
            else:
                months = num_creddits
        elif goldtype == "gift":
            if months is None or months < 1:
                start_over = True

            if thing:
                recipient = Account._byID(thing.author_id, data=True)
                if recipient._deleted:
                    thing = None
                    recipient = None
                    start_over = True
            elif not recipient:
                start_over = True
        else:
            goldtype = ""
            start_over = True

        if start_over:
            # If we have a form that didn't validate, and we're on the payment
            # page, redirect to the form, passing all of our form fields
            # (which are currently GET parameters).
            if is_payment:
                g.stats.simple_event("gold.checkout_redirects.to_form")
                qs = query_string(request.GET)
                return self.redirect('/gold' + qs)

            can_subscribe = (c.user_is_loggedin and
                             not c.user.has_gold_subscription)
            if not can_subscribe and goldtype == "autorenew":
                self.redirect("/creddits", code=302)
                
            return BoringPage(_("reddit gold"),
                              show_sidebar=False,
                              content=Gold(goldtype, period, months, signed,
                                           email, recipient,
                                           giftmessage,
                                           can_subscribe=can_subscribe,
                                           edit=edit),
                              page_classes=["gold-page", "gold-signup"],
                              ).render()
        else:
            # If we have a validating form, and we're not yet on the payment
            # page, redirect to it, passing all of our form fields
            # (which are currently GET parameters).
            if not is_payment:
                g.stats.simple_event("gold.checkout_redirects.to_payment")
                qs = query_string(request.GET)
                return self.redirect('/gold/payment' + qs)

            payment_blob = dict(goldtype=goldtype,
                                status="initialized")
            if c.user_is_loggedin:
                payment_blob["account_id"] = c.user._id
                payment_blob["account_name"] = c.user.name
            else:
                payment_blob["email"] = email

            if goldtype == "gift":
                payment_blob["signed"] = signed
                payment_blob["recipient"] = recipient.name
                payment_blob["giftmessage"] = _force_utf8(giftmessage)
                if thing:
                    payment_blob["thing"] = thing._fullname

            passthrough = generate_blob(payment_blob)

            page_classes = ["gold-page", "gold-payment"]
            if goldtype == "creddits":
                page_classes.append("creddits-payment")

            return BoringPage(_("reddit gold"),
                              show_sidebar=False,
                              content=GoldPayment(goldtype, period, months,
                                                  signed, recipient,
                                                  giftmessage, passthrough,
                                                  thing),
                              page_classes=page_classes,
                              ).render()

    def GET_creddits(self):
        return BoringPage(_("purchase creddits"),
                          show_sidebar=False,
                          content=Creddits(),
                          page_classes=["gold-page", "creddits-purchase"],
                          ).render()

    @validate(VUser())
    def GET_subscription(self):
        user = c.user
        content = GoldSubscription(user)
        return BoringPage(_("reddit gold subscription"), show_sidebar=False,
                          content=content).render()
