# This code was forked from the LiveJournal project owned and operated
# by Live Journal, Inc. The code has been modified and expanded by
# Dreamwidth Studios, LLC. These files were originally licensed under
# the terms of the license supplied by Live Journal, Inc, which can
# currently be found at:
#
# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt
#
# In accordance with the original license, this code and all its
# modifications are provided under the GNU General Public License.
# A copy of that license can be found in the LICENSE file included as
# part of this distribution.

package LJ::User;
use strict;
no warnings 'uninitialized';

use Carp;
use DW::SiteScheme;
use DW::Template;

use LJ::S2;
use LJ::S2Theme;
use LJ::Customize;

########################################################################
###  24. Styles and S2-Related Functions

=head2 Styles and S2-Related Functions
=cut

sub display_journal_deleted {
    my ( $u, $remote, %opts ) = @_;
    return undef unless LJ::isu($u);

    my $r = DW::Request->get;
    $r->status(404);

    my $extra = {};
    if ( $opts{bml} ) {
        $extra->{scope}      = 'bml';
        $extra->{scope_data} = $opts{bml};
    }
    elsif ( $opts{journal_opts} ) {
        $extra->{scope}      = 'journal';
        $extra->{scope_data} = $opts{journal_opts};
    }

    #get information on who deleted the account.
    my $deleter_name_html;
    if ( $u->is_community ) {
        my $userid       = $u->userid;
        my $logtime      = $u->statusvisdate_unix;
        my $dbcr         = LJ::get_cluster_reader($u);
        my ($deleter_id) = $dbcr->selectrow_array(
            "SELECT remoteid FROM userlog" . " WHERE userid=? AND logtime=? LIMIT 1",
            undef, $userid, $logtime );
        my $deleter_name = LJ::get_username($deleter_id);
        $deleter_name_html = $deleter_name ? LJ::ljuser($deleter_name) : 'Unknown';
    }
    else {
        #If this isn't a community, it can only have been deleted by the
        # journal owner.
        $deleter_name_html = LJ::ljuser($u);
    }

    #Information to pass to the "deleted account" template
    my $data = {
        reason => $u->prop('delete_reason'),
        u      => $u,

        #Showing an earliest purge date of 29 days after deletion, not 30,
        # to be safe with time zones.
        purge_date => LJ::mysql_date( $u->statusvisdate_unix + ( 29 * 24 * 3600 ), 0 ),

        deleter_name_html => $deleter_name_html,
        u_name_html       => LJ::ljuser($u),

        is_comm      => $u->is_community,
        is_protected => LJ::User->is_protected_username( $u->user ),
    };

    if ($remote) {
        $data = {
            %$data,

            logged_in => 1,

            #booleans for comms
            is_admin => $u->is_community
                && $remote->can_manage($u),
            is_sole_admin => $u->is_community
                && $remote->can_manage($u)
                && scalar( $u->maintainer_userids ) == 1,
            is_member_or_watcher => $u->is_community
                && ( $remote->member_of($u) || $remote->watches($u) ),

            #booleans for personal journals
            is_remote        => $u->equals($remote),
            has_relationship => $remote->watches($u) || $remote->trusts($u),
        };

        #construct relationship description & link
        my $relationship_ml;
        my @relationship_links;
        if ( $u->is_community
            && !( $remote->can_manage($u) && scalar( $u->maintainer_userids ) == 1 ) )
        {
            #don't offer the last admin of a deleted community a link to leave it
            my $watching = $remote->watches($u);
            my $memberof = $remote->member_of($u);

            if ( $watching && $memberof ) {
                $relationship_ml    = 'web.controlstrip.status.memberwatcher';
                @relationship_links = (
                    {
                        ml  => 'web.controlstrip.links.leavecomm',
                        url => "$LJ::SITEROOT/circle/$u->{user}/edit"
                    }
                );
            }
            elsif ($watching) {
                $relationship_ml    = 'web.controlstrip.status.watcher';
                @relationship_links = (
                    {
                        ml  => 'web.controlstrip.links.removecomm',
                        url => "$LJ::SITEROOT/circle/$u->{user}/edit"
                    }
                );
            }
            elsif ($memberof) {
                $relationship_ml    = 'web.controlstrip.status.member';
                @relationship_links = (
                    {
                        ml  => 'web.controlstrip.links.leavecomm',
                        url => "$LJ::SITEROOT/circle/$u->{user}/edit"
                    }
                );
            }
        }

        if ( !$u->is_community && !$remote->equals($u) ) {

            #Check that it isn't the deleted account's owner, otherwise we'd
            #tell them that they had granted access to themselves!
            my $trusts  = $remote->trusts($u);
            my $watches = $remote->watches($u);

            if ( $trusts && $watches ) {
                $relationship_ml    = 'web.controlstrip.status.trust_watch';
                @relationship_links = (
                    {
                        ml  => 'web.controlstrip.links.modifycircle',
                        url => "$LJ::SITEROOT/circle/$u->{user}/edit"
                    }
                );
            }
            elsif ($trusts) {
                $relationship_ml    = 'web.controlstrip.status.trusted';
                @relationship_links = (
                    {
                        ml  => 'web.controlstrip.links.modifycircle',
                        url => "$LJ::SITEROOT/circle/$u->{user}/edit"
                    }
                );
            }
            elsif ($watches) {
                $relationship_ml    = 'web.controlstrip.status.watched';
                @relationship_links = (
                    {
                        ml  => 'web.controlstrip.links.modifycircle',
                        url => "$LJ::SITEROOT/circle/$u->{user}/edit"
                    }
                );
            }
        }

        $data->{relationship_ml}    = $relationship_ml     if $relationship_ml;
        $data->{relationship_links} = \@relationship_links if @relationship_links;

    }

    return DW::Template->render_template_misc( "journal/deleted.tt", $data, $extra );
}

# returns undef on error, or otherwise arrayref of arrayrefs,
# each of format [ year, month, day, count ] for all days with
# non-zero count.  examples:
#  [ [ 2003, 6, 5, 3 ], [ 2003, 6, 8, 4 ], ... ]
#
sub get_daycounts {
    my ( $u, $remote, $not_memcache ) = @_;
    return undef unless LJ::isu($u);
    my $uid = $u->id;

    my $memkind  = 'p';                       # public only, changed below
    my $secwhere = "AND security='public'";
    my $viewall  = 0;

    if ( LJ::isu($remote) ) {

        # do they have the viewall priv?
        my $r       = DW::Request->get;
        my %getargs = %{ $r->get_args };
        if ( defined $getargs{'viewall'} and $getargs{'viewall'} eq '1' ) {
            $viewall = $remote->has_priv( 'canview', '*' );
            LJ::statushistory_add( $u->userid, $remote->userid, "viewall", "archive" ) if $viewall;
        }

        if ( $viewall || $remote->can_manage($u) ) {
            $secwhere = "";     # see everything
            $memkind  = 'a';    # all
        }
        elsif ( $remote->is_individual ) {
            my $gmask = $u->is_community ? $remote->member_of($u) : $u->trustmask($remote);
            if ($gmask) {
                $secwhere =
                    "AND (security='public' OR (security='usemask' AND allowmask & $gmask))";
                $memkind = 'g' . $gmask;    # friends case: allowmask == gmask == 1
            }
        }
    }

    my $memkey = [ $uid, "dayct2:$uid:$memkind" ];
    unless ($not_memcache) {
        my $list = LJ::MemCache::get($memkey);
        if ($list) {

            # this was an old version of the stored memcache value
            # where the first argument was the list creation time
            # so throw away the first argument
            shift @$list unless ref $list->[0];
            return $list;
        }
    }

    my $dbcr = LJ::get_cluster_def_reader($u) or return undef;
    my $sth  = $dbcr->prepare( "SELECT year, month, day, COUNT(*) "
            . "FROM log2 WHERE journalid=? $secwhere GROUP BY 1, 2, 3" );
    $sth->execute($uid);
    my @days;
    while ( my ( $y, $m, $d, $c ) = $sth->fetchrow_array ) {

        # we force each number from string scalars (from DBI) to int scalars,
        # so they store smaller in memcache
        push @days, [ int($y), int($m), int($d), int($c) ];
    }

    if ( $memkind ne "g1" && $memkind =~ /^g\d+$/ ) {

        # custom groups are cached for only 15 minutes
        LJ::MemCache::set( $memkey, [@days], 15 * 60 );
    }
    else {
        # all other security levels are cached indefinitely
        # because we clear them when there are updates
        LJ::MemCache::set( $memkey, [@days] );
    }
    return \@days;
}

sub meta_discovery_links {
    my $u           = shift;
    my $journalbase = $u->journal_base;

    my %opts = ref $_[0] ? %{ $_[0] } : @_;

    my $ret = "";

    # Automatic Discovery of RSS/Atom
    if ( $opts{feeds} ) {
        if ( $opts{tags} && @{ $opts{tags} || [] } ) {
            my $taglist = join( ',', map( { LJ::eurl($_) } @{ $opts{tags} || [] } ) );
            $ret .=
qq{<link rel="alternate" type="application/rss+xml" title="RSS: filtered by selected tags" href="$journalbase/data/rss?tag=$taglist" />\n};
            $ret .=
qq{<link rel="alternate" type="application/atom+xml" title="Atom: filtered by selected tags" href="$journalbase/data/atom?tag=$taglist" />\n};
        }

        $ret .=
qq{<link rel="alternate" type="application/rss+xml" title="RSS: all entries" href="$journalbase/data/rss" />\n};
        $ret .=
qq{<link rel="alternate" type="application/atom+xml" title="Atom: all entries" href="$journalbase/data/atom" />\n};
        $ret .=
qq{<link rel="service" type="application/atomsvc+xml" title="AtomAPI service document" href="}
            . $u->atom_service_document
            . qq{" />\n};
    }

    # OpenID Server
    $ret .= $u->openid_tags if $opts{openid};

    return $ret;
}

sub opt_ctxpopup {
    my $u = shift;

    # if unset, default to on
    my $prop = $u->raw_prop('opt_ctxpopup') || 'Y';

    return $prop;
}

# should contextual hover be displayed for icons
sub opt_ctxpopup_icons {
    return ( $_[0]->prop('opt_ctxpopup') eq "Y" || $_[0]->prop('opt_ctxpopup') eq "I" );
}

# should contextual hover be displayed for the graphical userhead
sub opt_ctxpopup_userhead {
    return ( $_[0]->prop('opt_ctxpopup') eq "Y" || $_[0]->prop('opt_ctxpopup') eq "U" );
}

sub opt_embedplaceholders {
    my $u = shift;

    my $prop = $u->raw_prop('opt_embedplaceholders');

    if ( defined $prop ) {
        return $prop;
    }
    else {
        my $imagelinks = $u->prop('opt_imagelinks');
        return $imagelinks;
    }
}

sub set_default_style {
    my $style = eval { LJ::Customize->verify_and_load_style( $_[0] ); };
    warn $@ if $@;

    return $style;
}

sub show_control_strip {
    my $u = shift;

    LJ::Hooks::run_hook( 'control_strip_propcheck', $u, 'show_control_strip' )
        if LJ::is_enabled('control_strip_propcheck');

    my $prop = $u->raw_prop('show_control_strip');
    return 0 if $prop =~ /^off/;

    return 'dark' if $prop eq 'forced';

    return $prop;
}

sub view_control_strip {
    my $u = shift;

    LJ::Hooks::run_hook( 'control_strip_propcheck', $u, 'view_control_strip' )
        if LJ::is_enabled('control_strip_propcheck');

    my $prop = $u->raw_prop('view_control_strip');
    return 0 if $prop =~ /^off/;

    return 'dark' if $prop eq 'forced';

    return $prop;
}

# BE VERY CAREFUL about the return values and arguments you pass to this
# method.  please understand the security implications of this, and how to
# properly and safely use it.
#
sub view_priv_check {
    my ( $remote, $u, $requested, $page, $itemid ) = @_;

    # $requested is set to the 'viewall' GET argument.  this should ONLY be on if the
    # user is EXPLICITLY requesting to view something they can't see normally.  most
    # of the time this is off, so we can bail now.
    return unless $requested;

    # now check the rest of our arguments for validity
    return unless LJ::isu($remote) && LJ::isu($u);
    return if defined $page   && $page   !~ /^\w+$/;
    return if defined $itemid && $itemid !~ /^\d+$/;

    # viewsome = "this user can view suspended content"
    my $viewsome = $remote->has_priv( canview => 'suspended' );

    # viewall = "this user can view all content, even private"
    my $viewall = $viewsome && $remote->has_priv( canview => '*' );

    # make sure we log the content being viewed
    if ( $viewsome && $page ) {
        my $user = $u->user;
        $user .= ", itemid: $itemid" if defined $itemid;
        my $sv = $u->statusvis;
        LJ::statushistory_add( $u->userid, $remote->userid, 'viewall',
            "$page: $user, statusvis: $sv" );
    }

    return wantarray ? ( $viewall, $viewsome ) : $viewsome;
}

=head2 C<< $u->viewing_style( $view ) >>
Takes a user and a view argument and returns what that user's preferred
style is for a given view.
=cut

sub viewing_style {
    my ( $u, $view ) = @_;

    $view ||= 'entry';

    my %style_types = ( O => "original", M => "mine", S => "site", L => "light" );
    my %view_props  = (
        entry => 'opt_viewentrystyle',
        reply => 'opt_viewentrystyle',
        icons => 'opt_viewiconstyle',
    );

    my $prop = $view_props{$view} || 'opt_viewjournalstyle';
    return 'original' unless defined $u->prop($prop);
    return $style_types{ $u->prop($prop) } || 'original';
}

########################################################################
### End LJ::User functions

########################################################################
### Begin LJ functions

package LJ;

use Carp;

########################################################################
###  24. Styles and S2-Related Functions

=head2 Styles and S2-Related Functions (LJ)
=cut

# FIXME: Update to pull out S1 support.
# <LJFUNC>
# name: LJ::make_journal
# class:
# des:
# info:
# args: dbarg, user, view, remote, opts
# des-:
# returns:
# </LJFUNC>
sub make_journal {
    my ( $user, $view, $remote, $opts ) = @_;

    my $r    = DW::Request->get;
    my $geta = $r->get_args;
    $opts->{getargs} = $geta;

    my $u = $opts->{'u'} || LJ::load_user($user);

    unless ($u) {
        ${ $opts->{handle_with_siteviews_ref} } = 1;
        return DW::Template->render_template_misc(
            "error/unknown-user.tt",
            { user  => $user },
            { scope => 'journal', scope_data => $opts }
        );
    }

    LJ::set_active_journal($u);

    my ($styleid);
    if ( $opts->{'styleid'} ) {    # s1 styleid
        confess 'S1 was removed, sorry.';
    }
    else {

        $view ||= "lastn";         # default view when none specified explicitly in URLs
        if (   $LJ::viewinfo{$view}
            || $view eq "month"
            || $view eq "entry"
            || $view eq "reply" )
        {
            $styleid = -1
                ; # to get past the return, then checked later for -1 and fixed, once user is loaded.
        }
        else {
            $opts->{'badargs'} = 1;
        }
    }
    return unless $styleid;

    $u->{'_journalbase'} = $u->journal_base( vhost => $opts->{'vhost'} );

    my $eff_view = $LJ::viewinfo{$view}->{'styleof'} || $view;

    my @needed_props = (
        "stylesys",             "s2_style",
        "url",                  "urlname",
        "opt_nctalklinks",      "renamedto",
        "opt_blockrobots",      "opt_usesharedpic",
        "icbm",                 "journaltitle",
        "journalsubtitle",      "adult_content",
        "opt_viewjournalstyle", "opt_viewentrystyle"
    );

    # preload props the view creation code will need later (combine two selects)
    if ( ref $LJ::viewinfo{$eff_view}->{'owner_props'} eq "ARRAY" ) {
        push @needed_props, @{ $LJ::viewinfo{$eff_view}->{'owner_props'} };
    }

    $u->preload_props(@needed_props);

    # if the remote is the user to be viewed, make sure the $remote
    # hashref has the value of $u's opt_nctalklinks (though with
    # LJ::load_user caching, this may be assigning between the same
    # underlying hashref)
    $remote->{opt_nctalklinks} = $u->{opt_nctalklinks}
        if $remote && $remote->equals($u);

    # What style are we shooting for, based on user preferences and get arguments?
    my $stylearg = LJ::determine_viewing_style( $geta, $view, $remote );
    my $stylesys = 1;

    if ( $styleid == -1 ) {

        my $get_styleinfo = sub {

            # forced s2 style id (must be numeric)
            if ( $geta->{s2id} && $geta->{s2id} =~ /^\d+$/ ) {

                # get the owner of the requested style
                my $style = LJ::S2::load_style( $geta->{s2id} );
                my $owner = $style && $style->{userid} ? $style->{userid} : 0;

                # remote can use s2id on this journal if:
                # owner of the style is remote or managed by remote OR
                # owner of the style has s2styles cap and remote is viewing owner's journal OR
                # all layers in this style are public (public layer or is_public)

                if ( $u->id == $owner && $u->get_cap("s2styles") ) {
                    $opts->{'style_u'} = LJ::load_userid($owner);
                    return ( 2, $geta->{'s2id'} );
                }

                if ( $remote && $remote->can_manage($owner) ) {

                    # check is owned style still available: paid user possible became plus...
                    my $lay_id   = $style->{layer}->{layout};
                    my $theme_id = $style->{layer}->{theme};
                    my %lay_info;
                    LJ::S2::load_layer_info( \%lay_info,
                        [ $style->{layer}->{layout}, $style->{layer}->{theme} ] );

                    if (    LJ::S2::can_use_layer( $remote, $lay_info{$lay_id}->{redist_uniq} )
                        and LJ::S2::can_use_layer( $remote, $lay_info{$theme_id}->{redist_uniq} ) )
                    {
                        $opts->{'style_u'} = LJ::load_userid($owner);
                        return ( 2, $geta->{'s2id'} );
                    }    # else this style not allowed by policy
                }

                return ( 2, $geta->{s2id} ) if LJ::S2::style_is_public($style);
            }

            # style=mine passed in GET or userprop to use mine?
            if ( $remote && $stylearg eq 'mine' ) {

                # get remote props and decide what style remote uses
                $remote->preload_props( "stylesys", "s2_style" );

                # remote using s2; make sure we pass down the $remote object as the style_u to
                # indicate that they should use $remote to load the style instead of the regular $u
                if ( $remote->{'stylesys'} == 2 && $remote->{'s2_style'} ) {
                    $opts->{'checkremote'} = 1;
                    $opts->{'style_u'}     = $remote;
                    return ( 2, $remote->{'s2_style'} );
                }

                # return stylesys 2; will fall back on default style
                $opts->{style_u} = $remote;
                return ( 2, undef );
            }

            # resource URLs have the styleid in it
            # unless they're a special style, like sitefeeds (which have no styleid)
            # in which case, let them fall through. Something else will handle it
            if ( $view eq "res" && $opts->{'pathextra'} =~ m!^/(\d+)/! && $1 ) {
                return ( 2, $1 );
            }

            # feed accounts have a special style
            if ( $u->is_syndicated && %$LJ::DEFAULT_FEED_STYLE ) {
                return ( 2, "sitefeeds" );
            }

            my $forceflag = 0;
            LJ::Hooks::run_hooks( "force_s1", $u, \$forceflag );

            # if none of the above match, they fall through to here
            if ( !$forceflag && $u->{'stylesys'} == 2 ) {
                return ( 2, $u->{'s2_style'} );
            }

            # no special case, let it fall back on the default
            return ( 2, undef );
        };

        ( $stylesys, $styleid ) = $get_styleinfo->();
    }

    # transcode the tag filtering information into the tag getarg; this has to
    # be done above the s1shortcomings section so that we can fall through to that
    # style for lastn filtered by tags view
    if ( $view eq 'lastn' && $opts->{pathextra} && $opts->{pathextra} =~ /^\/tag\/(.+)$/ ) {
        $opts->{getargs}->{tag} = LJ::durl($1);
        $opts->{pathextra} = undef;
    }

    # do the same for security filtering
    elsif (( $view eq 'lastn' || $view eq 'read' )
        && $opts->{pathextra}
        && $opts->{pathextra} =~ /^\/security\/(.*)$/ )
    {
        $opts->{getargs}->{security} = LJ::durl($1);
        $opts->{pathextra} = undef;
    }

    $r->note( journalid => $u->userid )
        if $r;

    my $error = sub {
        my ( $file, $fileopts ) = @_;
        $fileopts //= {};

        ${ $opts->{handle_with_siteviews_ref} } = 1;
        return DW::Template->render_template_misc( $file, $fileopts,
            { scope => 'journal', scope_data => $opts } );
    };
    my $notice = sub {
        return $error->( 'error/vhost.tt', { u => $u, msg => $_[0] } );
    };

    if (   $opts->{'vhost'} eq "users"
        && !$u->is_redirect
        && !LJ::get_cap( $u, "userdomain" ) )
    {
        return $notice->(
            LJ::Lang::ml( 'error.vhost.nodomain1', { user_domain => $LJ::USER_DOMAIN } ) );
    }
    if ( $opts->{'vhost'} =~ /^other:/ ) {
        return $notice->( LJ::Lang::ml('error.vhost.noalias1') );
    }
    if ( $opts->{'vhost'} eq "community" && $u->journaltype !~ /[CR]/ ) {
        $opts->{'badargs'} = 1;
        return;    # 404 Not Found
    }

    if ( $view eq "network" && !LJ::get_cap( $u, "friendsfriendsview" ) ) {
        my $errmsg = LJ::Lang::ml('cprod.friendsfriendsinline.text3.v1');
        return $error->( 'error.tt', { message => $errmsg } );
    }

    # signal to LiveJournal.pm that we can't handle this
    if ( $stylesys == 1 || $stylearg eq 'site' || $stylearg eq 'light' ) {

        # If they specified ?format=light, it means they want a page easy
        # to deal with text-only or on a mobile device.  For now that means
        # render it in the lynx site scheme.
        DW::SiteScheme->set_for_request('lynx')
            if $stylearg eq 'light';

        # Render a system-owned S2 style that renders
        # this content, then passes it to get treated as BML
        $stylesys = 2;
        $styleid  = "siteviews";
    }

    # now, if there's a GET argument for tags, split those out
    if ( exists $opts->{getargs}->{tag} ) {
        my $tagfilter = $opts->{getargs}->{tag};

        unless ($tagfilter) {
            $opts->{redir} = $u->journal_base . "/tag/";
            return;
        }

        # error if disabled
        return $error->( "error/tagview.tt", { errmsg => 'error.tag.disabled' } )
            unless LJ::is_enabled('tags');

        # throw an error if we're rendering in S1, but not for renamed accounts
        if ( $stylesys == 1 && $view ne 'data' && !$u->is_redirect ) {
            return $error->( "error/tagview.tt", { errmsg => 'error.tag.s1' } );
        }

        # overwrite any tags that exist
        $opts->{tags} = [];
        my $check_tagstring = LJ::Tags::is_valid_tagstring( $tagfilter, $opts->{tags},
            { omit_underscore_check => 1 } );

        return $error->( "error/tagview.tt", { errmsg => 'error.tag.invalid' } )
            unless $check_tagstring;

        # get user's tags so we know what remote can see, and setup an inverse mapping
        # from keyword to tag
        $opts->{tagids} = [];
        my $tags  = LJ::Tags::get_usertags( $u, { remote => $remote } );
        my %kwref = ( map { $tags->{$_}->{name}          => $_ } keys %{ $tags || {} } );

        foreach ( @{ $opts->{tags} } ) {
            return $error->( "error/tagview.tt", { errmsg => 'error.tag.undef' } )
                unless $kwref{$_};

            push @{ $opts->{tagids} }, $kwref{$_};
        }

        my $tagmode = $opts->{getargs}->{mode} || '';
        $opts->{tagmode} = $tagmode eq 'and' ? 'and' : 'or';

        # also allow mode=all (equivalent to 'and')
        $opts->{tagmode} = 'and' if $tagmode eq 'all';
    }

    # validate the security filter
    if ( exists $opts->{getargs}->{security} ) {
        my $securityfilter = $opts->{getargs}->{security};

        my $canview_groups = (
            $view eq "lastn"    # viewing recent entries
                  # ... or your own read page (can't see locked entries on others' read page anyway)
                || ( $view eq "read" && $u->equals($remote) )
        );

        my $r            = DW::Request->get;
        my $security_err = sub {
            my ( $args, %opts ) = @_;

            my $status = $opts{status} || $r->NOT_FOUND;

            my @levels;
            my @groups;

            # error message is an appropriate type to show the list
            if ( $opts{show_list} && $canview_groups ) {

                my $path = $view eq "read" ? "/read/security" : "/security";
                @levels = (
                    {
                        link    => LJ::create_url( "$path/public", viewing_style => 1 ),
                        name_ml => "label.security.public"
                    }
                );

                if ( $u->is_comm ) {
                    push @levels,
                        {
                        link    => LJ::create_url( "$path/access", viewing_style => 1 ),
                        name_ml => "label.security.members"
                        }
                        if $remote && $remote->member_of($u);

                    push @levels,
                        {
                        link    => LJ::create_url( "$path/private", viewing_style => 1 ),
                        name_ml => "label.security.maintainers"
                        }
                        if $remote && $remote->can_manage_other($u);
                }
                else {
                    push @levels,
                        {
                        link    => LJ::create_url( "$path/access", viewing_style => 1 ),
                        name_ml => "label.security.accesslist"
                        }
                        if $u->trusts($remote);

                    push @levels,
                        {
                        link    => LJ::create_url( "$path/private", viewing_style => 1 ),
                        name_ml => "label.security.private"
                        }
                        if $u->equals($remote);
                }

                $args->{levels} = \@levels;

                @groups = map {
                    {
                        link => LJ::create_url( "$path/group:" . $_->{groupname} ),
                        name => $_->{groupname}
                    }
                } $remote->trust_groups if $u->equals($remote);
                $args->{groups} = \@groups;
            }

            ${ $opts->{handle_with_siteviews_ref} } = 1;
            my $ret = DW::Template->template_string(
                "journal/security.tt",
                $args,
                {
                    status => $status,
                }
            );
            $opts->{siteviews_extra_content} = $args->{sections};
            return $ret;
        };

        return $security_err->( { message => undef }, show_list => 1 )
            unless $securityfilter;

        return $security_err->( { message => "error.security.nocap2" }, status => $r->FORBIDDEN )
            unless LJ::get_cap( $remote, "security_filter" )
            || LJ::get_cap( $u, "security_filter" );

        return $security_err->( { message => "error.security.disabled2" } )
            unless LJ::is_enabled("security_filter");

        # throw an error if we're rendering in S1, but not for renamed accounts
        return $security_err->( { message => "error.security.s1.2" } )
            if $stylesys == 1 && $view ne 'data' && !$u->is_redirect;

        # check the filter itself
        if ( lc($securityfilter) eq 'friends' ) {
            $opts->{securityfilter} = 'access';
        }
        elsif ( $securityfilter =~ /^(?:public|access|private)$/i ) {
            $opts->{securityfilter} = lc($securityfilter);

            # see if they want to filter by a custom group
        }
        elsif ( $securityfilter =~ /^group:(.+)$/i && $canview_groups ) {
            my $tf = $u->trust_groups( name => $1 );
            if (
                $tf
                && (   $u->equals($remote)
                    || $u->trustmask($remote) & ( 1 << $tf->{groupnum} ) )
                )
            {
                # let them filter the results page by this group
                $opts->{securityfilter} = $tf->{groupnum};
            }
        }

        return $security_err->( { message => "error.security.invalid2" }, show_list => 1 )
            unless defined $opts->{securityfilter};
    }

    unless ( $geta->{'viewall'} && $remote && $remote->has_priv( "canview", "suspended" )
        || $opts->{'pathextra'} =~ m!/(\d+)/stylesheet$! )
    {    # don't check style sheets
        return $u->display_journal_deleted( $remote, journal_opts => $opts ) if $u->is_deleted;

        return $error->( "error/suspended.tt", { u => $u, remote => $remote } ) if $u->is_suspended;

        my $entry = $opts->{ljentry};
        return $error->( "error/suspended-entry.tt", { u => $u } )
            if $entry && $entry->is_suspended_for($remote);
    }

    return $error->("error/purged.tt") if $u->is_expunged;

    my %valid_identity_views = (
        read  => 1,
        res   => 1,    # res is a resource, such as an external stylesheet
        icons => 1,
    );

    if ( $u->is_identity && !$valid_identity_views{$view} ) {
        return $error->( "error/openid-user.tt", { u => $u } );
    }

    $opts->{'view'} = $view;

    # what charset we put in the HTML
    $opts->{'saycharset'} ||= "utf-8";

    if ( $view eq 'data' ) {
        return LJ::Feed::make_feed( $r, $u, $remote, $opts );
    }

    if ( $stylesys == 2 ) {
        eval { LJ::S2->can("dostuff") };    # force Class::Autouse

        my $mj;

        eval { $mj = LJ::S2::make_journal( $u, $styleid, $view, $remote, $opts ); };
        if ($@) {
            if ( $remote && $remote->show_raw_errors ) {
                my $r = DW::Request->get;
                $r->content_type("text/html");
                $r->print("<b>[Error: $@]</b>");
                warn $@;
                return;
            }
            else {
                die $@;
            }
        }

        return $mj;
    }

    # if we get here, then we tried to run the old S1 path, so die and hope that
    # somebody comes along to fix us :(
    confess 'Tried to run S1 journal rendering path.';
}

1;
