#!/usr/bin/perl
#
# DW::Controller::Profile
#
# Displays information about an account in a viewer friendly manner.
#
# Authors:
#      Mark Smith <mark@dreamwidth.org>
#      Janine Smith <janine@netrophic.com>
#      Jen Griffin <kareila@livejournal.com> -- TT conversion
#
# Copyright (c) 2009-2020 by Dreamwidth Studios, LLC.
#
# This program is free software; you may redistribute it and/or modify it under
# the same terms as Perl itself. For a copy of the license, please reference
# 'perldoc perlartistic' or 'perldoc perlgpl'.
#

package DW::Controller::Profile;

use strict;

use DW::Controller;
use DW::Routing;

DW::Routing->register_string( '/profile', \&profile_handler, app => 1 );

sub profile_handler {
    my ( $ok, $rv ) = controller( anonymous => 1 );
    return $rv unless $ok;

    my $r   = $rv->{r};
    my $get = $r->get_args;

    my $is_full = $get->{mode} && $get->{mode} eq 'full' ? 1 : 0;

    my $scope = '/profile/main.tt';

    my $remote = $rv->{remote};
    my $u;

    # figure out what $u should be
    {
        my $userarg = $get->{user};

        # when using user domain URLs, get userarg from the request notes,
        # which was set in LiveJournal.pm
        $userarg ||= $r->note('_journal');

        my $username = LJ::canonical_username($userarg);

        # usually we're going off username, but under some circumstances
        # we may be given a userid instead, so check for that first

        if ( my $userid = ( $get->{userid} || 0 ) + 0 ) {
            $u = LJ::load_userid($userid);

            # only users with finduser can view profiles by userid, unless
            # we are viewing the profile of an identity (OpenID) account
            unless ( ( $remote && $remote->has_priv('finduser') )
                || ( $get->{t} && $get->{t} eq "I" && $u && $u->is_identity ) )
            {
                return error_ml("$scope.error.reqfinduser");
            }
        }
        elsif ($username) {
            $u = LJ::load_user($username);

            # redirect identity accounts to standard identity url
            if ( $u && $u->is_identity ) {
                return $r->redirect( $u->profile_url( full => $is_full ) );
            }
        }
        elsif ($remote) {
            $u = $remote;
        }
        else {    # visited $LJ::SITEROOT/profile with no userargs while logged out
            return DW::Controller::needlogin();
        }

        # at this point, if we still don't have $u, give up
        return error_ml( "$scope.error.nonexist", { user => $username } ) unless $u;

        LJ::set_active_journal($u);
    }

    # error if account is purged
    return DW::Template->render_template('error/purged.tt') if $u->is_expunged;

    # redirect non-identity profiles to their subdomain urls
    if ( !$u->is_identity ) {
        my $url = $u->profile_url( full => $is_full );

        # use regexps to extract the user domain from $url and compare to $r
        my $good_domain = $url;
        $good_domain =~ s!^https?://!!;
        $good_domain =~ s!/.*!!;
        if ( $r->header_in("Host") ne $good_domain ) {
            return $r->redirect($url);
        }
    }

    # rename redirect?
    {
        my $renamed_u = $u->get_renamed_user;
        unless ( $u->equals($renamed_u) ) {
            my $urlargs = { user => $renamed_u->user };
            $urlargs->{mode} = 'full' if $is_full;
            return $r->redirect( LJ::create_url( '/profile', args => $urlargs ) );
        }
    }

    # can't view suspended/deleted profiles unless you have viewall
    my $viewall = 0;
    ($viewall) = $remote->view_priv_check( $u, $get->{viewall}, 'profile' ) if $remote;

    unless ($viewall) {
        return DW::Template->render_template( 'error/suspended.tt', { u => $u, remote => $remote } )
            if $u->is_suspended;

        return $u->display_journal_deleted($remote) if $u->is_deleted;
    }

    # DONE with error pages and redirects - begin building the page!

    LJ::need_res("js/profile.js");
    LJ::need_res( { priority => $LJ::OLD_RES_PRIORITY }, "stc/profile.css" );

    my $vars = { u => $u, remote => $remote, is_full => $is_full };

    $vars->{profile} = $u->profile_page( $remote, viewall => $viewall );

    # block robots?
    $vars->{robot_meta_tags} = LJ::robot_meta_tags()
        if !$u->is_visible || $u->should_block_robots;

    # TODO: stop profile choking on large numbers of subscribers or members
    $vars->{force_empty} = exists $LJ::FORCE_EMPTY_SUBSCRIPTIONS{ $u->id } ? 1 : 0;

    $vars->{load_userids} = sub { LJ::load_userids( @{ $_[0] } ) };

    $vars->{sort_by_username} = sub {
        my ( $list, $us ) = @_;
        my $sort = sub { $a->display_name cmp $b->display_name };
        $sort = sub { $us->{$a}->display_name cmp $us->{$b}->display_name }
            if $us;
        return [ sort $sort @$list ];
    };

    $vars->{createdate} = LJ::mysql_time( $u->timecreate );
    $vars->{accttype}   = DW::Pay::get_account_type_name($u);
    $vars->{expiretime} = sub {
        my $expiretime = DW::Pay::get_account_expiration_time($u);
        return DateTime->from_epoch( epoch => $expiretime )->date
            if $expiretime > 0;
    };

    # given a single item (scalar or hashref), linkify it and return string
    $vars->{linkify} = sub {
        my $l = $_[0];
        return $l unless ref $l eq 'HASH';

        if ( $l->{text} ) {
            my $ret = "";
            $ret .= $l->{secimg} if $l->{secimg};
            $ret .= $l->{url} ? qq(<a href="$l->{url}" rel="nofollow">$l->{text}</a>) : $l->{text};
            return $ret;
        }
        elsif ( $l->{email} ) {

            # the ehtml call here shouldn't be necessary, but just in case
            # they slip in an email that contains Bad Stuff, escape it
            my $mangled_email = LJ::CleanHTML::mangle_email_address( LJ::ehtml( $l->{email} ) );

            # return the mangled email with a privacy icon if there is one
            $mangled_email = $l->{secimg} . $mangled_email if $l->{secimg};
            return $mangled_email;
        }
        else {
            return LJ::Lang::ml("$scope.error.linkify");
        }
    };

    # given multiple items in an arrayref, linkify each one appropriately
    # and return them as one string with the join_string separating each item
    # (the join_string will be the first item in the arrayref)
    $vars->{linkify_multiple} = sub {
        my $r = $_[0];
        return $r unless ref $r eq 'ARRAY';

        return $vars->{linkify}->( $r->[0] ) unless @$r > 1;

        my $join_string = shift @$r;
        my @links;
        foreach my $l (@$r) {
            next unless $l;
            push @links, $vars->{linkify}->($l);
        }

        return join( $join_string, @links );
    };

    # helper function for repetitive link array construction
    $vars->{cb_links} = sub {
        my $opts = $_[0];
        my @ret;
        push @ret, { url => $opts->{editurl}, text => LJ::Lang::ml("$scope.section.edit") }
            if $remote && $remote->can_manage($u) && $opts->{editurl};
        my $extra = $opts->{extra} // [];
        push( @ret, $_ ) foreach @$extra;
        return \@ret;
    };

    # code for separating OpenID users by site
    $vars->{parse_openids} = sub {
        my ($openids) = @_;
        return unless $openids;

        my %sites;
        my %shortnames;

        my $sitestore = sub {
            my ( $site, $u, $name ) = @_;
            $sites{$site}      ||= [];
            $shortnames{$site} ||= [];

            push @{ $sites{$site} },      $u;
            push @{ $shortnames{$site} }, $name;
        };

        # TODO: use DW::External methods here?
        foreach my $u (@$openids) {
            my $id    = $u->display_name;
            my @parts = split /\./, $id;
            if ( @parts < 2 ) {

                # we don't know how to parse this, so don't
                $sitestore->( 'unknown', $u, $id );
                next;
            }

            my ( $name, $site );

            # if this looks like a URL, hope the username is at the end
            if ( $parts[-1] =~ m=/([^/]+)/?$= ) {
                $name = $1;
                ($site) = ( $id =~ m=([^/.]+\.[^/]+)= );

            }
            else {    # assume the username is the hostname
                my $host = shift @parts;
                ($name) = ( $host =~ m=([^/]+)$= );
                $site = join '.', @parts;
            }

            $sitestore->( $site, $u, $name );
        }

        return { sites => \%sites, shortnames => \%shortnames };
    };

    # organize filtering subs for various watch/trust/etc. lists
    $vars->{includeuser} = {
        trusted                 => sub { 1 },
        trusted_by              => sub { $is_full || !$_[0]->is_inactive },
        mutually_trusted        => sub { $_[0]->is_individual },
        not_mutually_trusted    => sub { 1 },
        not_mutually_trusted_by => sub { $is_full || !$_[0]->is_inactive },
        watched => sub {    # this one filters on journaltype
            ( $_[0]->journaltype =~ /^[\Q$_[1]\E]$/ )    #  'PI', 'C', 'Y'
                && ( $_[1] eq 'C' ? $is_full || !$_[0]->is_inactive : 1 );
        },
        watched_by              => sub { $is_full || !$_[0]->is_inactive },
        mutually_watched        => sub { $_[0]->is_individual },
        not_mutually_watched    => sub { $_[0]->is_individual },
        not_mutually_watched_by => sub { $is_full || !$_[0]->is_inactive },
        members                 => sub { 1 },
        member_of               => sub { $is_full || !$_[0]->is_inactive },
        admin_of                => sub { $is_full || !$_[0]->is_inactive },
        posting_access_to       => sub { $is_full || !$_[0]->is_inactive },
        posting_access_from     => sub { 1 },
    };

    return DW::Template->render_template( 'profile/main.tt', $vars );
}

1;
