#!/usr/bin/perl
#
# DW::Controller::Tools
#
#
# Authors:
#      RSH <ruth.s.hatch@gmail.com
#
# 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::Tools;
use strict;
use warnings;
use DW::Controller;
use DW::Routing;
use DW::Template;
use DW::FormErrors;
use HTTP::Date;    # str2time

DW::Routing->register_string( '/tools/comment_crosslinks', \&crosslinks_handler,        app => 1 );
DW::Routing->register_string( '/tools/recent_emailposts',  \&recent_emailposts_handler, app => 1 );
DW::Routing->register_string( '/tools/opml',               \&opml_handler,              app => 1 );
DW::Routing->register_string( '/tools/emailmanage',        \&emailmanage_handler,       app => 1 );
DW::Routing->register_string( '/tools/tellafriend',        \&tellafriend_handler,       app => 1 );

sub crosslinks_handler {
    my ( $ok, $rv ) = controller( authas => 1 );
    return $rv unless $ok;
    my $u = $rv->{u};

    my $dbcr = LJ::get_cluster_reader($u) or die;

    my $props = $dbcr->selectall_arrayref(
        q{
select
    l.jitemid * 256 + l.anum as 'ditemid',
    t.jtalkid * 256 + l.anum as 'dtalkid',
    tp.value
from
    log2 l
       inner join talk2 t on (t.journalid = l.journalid and l.jitemid = t.nodeid and t.nodetype = 'L')
       inner join talkprop2 tp on (tp.journalid = t.journalid and t.jtalkid = tp.jtalkid)
where
    tp.tpropid = 13 and tp.journalid = ?
		}, undef, $u->id
    );

    my $base = $u->journal_base;

    my $vars = { 'base' => $base, 'props' => $props, 'authas_html' => $rv->{authas_html} };
    return DW::Template->render_template( 'tools/comment_crosslinks.tt', $vars );
}

sub recent_emailposts_handler {
    my ( $ok, $rv ) = controller( authas => 1 );
    return $rv unless $ok;
    my $remote = $rv->{remote};
    my $r      = DW::Request->get;
    my $vars   = {};

    unless ($LJ::EMAIL_POST_DOMAIN) {
        $r->add_msg( "Sorry, this site is not configured to use the emailgateway.", $r->WARNING );
        return $r->redirect($LJ::SITEROOT);
    }

    my $admin = $remote->has_priv('supporthelp');
    $vars->{admin} = $admin;

    my $u;
    if ($admin) {
        my $user = $r->post_args->{user} || $r->get_args->{user};
        $u = LJ::load_user($user);
        $vars->{user} = $user;
    }

    $u ||= $remote;

    if ($u) {
        my @clean_rows;
        my $dbcr = LJ::get_cluster_reader($u);
        my $sql  = qq{
            SELECT
                logtime, extra,
                DATE_FORMAT(FROM_UNIXTIME(logtime), "%M %D %Y, %l:%i%p") AS ftime
            FROM userlog
            WHERE userid=?
                AND action='emailpost'
            ORDER BY logtime DESC LIMIT 50
        };
        my $data = $dbcr->selectall_hashref( $sql, 'logtime', undef, $u->{userid} );

        foreach ( reverse sort keys %$data ) {
            my $row = {};
            LJ::decode_url_string( $data->{$_}->{extra}, $row );

            my $err;
            if ( $row->{e} ) {
                $err = '<strong>Yes</strong>';
                $err .= ' (will retry)' if $row->{retry};
            }
            else {
                $err = 'None';
            }
            my $temp = {
                when => $data->{$_}->{ftime},
                type => ( $row->{t} || "entry" ),
                subj => $row->{s},
                err  => $err,
                msg  => ( $row->{m} ? LJ::ehtml( $row->{m} ) : 'Post success.' )
            };
            push @clean_rows, $temp;
        }

        $vars->{data} = \@clean_rows;
    }
    else {
        $r->add_msg( "No such user.", $r->WARNING );
    }

    return DW::Template->render_template( 'tools/recent_emailposts.tt', $vars );

}

sub opml_handler {
    my ( $ok, $rv ) = controller( authas => 1 );
    return $rv unless $ok;
    my $remote   = $rv->{remote};
    my $r        = DW::Request->get;
    my $get_args = $r->get_args;
    my $user     = $get_args->{user};

    # if we don't have a current user but somebody is logged in, redirect
    # them to their own OPML page
    if ( $remote && !$user ) {
        return BML::redirect("$LJ::SITEROOT/tools/opml?user=$remote->{user}");
    }

    return "No 'user' argument" unless $user;

    my $u = LJ::load_user_or_identity($user)
        or return "Invalid user.";

    my @uids;

    # different accounts need different userid loads
    # will use watched accounts for personal accounts
    # and identity accounts
    # and members for communities
    if ( $u->is_person || $u->is_identity ) {
        @uids = $u->watched_userids;
    }
    elsif ( $u->is_community ) {
        @uids = $u->member_userids;
    }
    else {
        return "Invalid account type.";
    }

    my $us = LJ::load_userids(@uids);

    DW::Stats::increment( 'dw.opml.used', 1 );
    my @cleaned;

    # currently ordered by user ID; there might be a better way to order this
    # but unless somebody has a strong preference about it there's no point
    foreach my $uid ( sort { $a <=> $b } @uids ) {
        my $w = $us->{$uid} or next;

        # identity accounts do not have feeds
        next if $w->is_identity;

        # filter by account type
        next
            if $get_args->{show}
            && !( $get_args->{show} =~ /[P]/ && $w->is_person
            || $get_args->{show} =~ /[C]/  && $w->is_community
            || $get_args->{show} =~ /[YF]/ && $w->is_syndicated );

        my $title;

        # use the username + site abbreviation for each feed's title if we have that
        # option set
        if ( $get_args->{title} eq "username" ) {
            $title = $w->display_username . " (" . $LJ::SITENAMEABBREV . ")";
        }
        else {
            # FIXXME: Should be using a function call instead! But $w->prop( "journaltitle" )
            # returns empty here, though it's used in profile.bml
            $title = $w->{name};
        }

        my $feed;

        if ( $w->is_syndicated ) {
            my $synd = $w->get_syndicated;
            $feed = $synd->{synurl};
        }
        else {
            $feed = $w->journal_base;

            if ( $get_args->{feed} eq "atom" ) {
                $feed .= "/data/atom";
            }
            else {
                $feed .= "/data/rss";
            }

            if ( $get_args->{auth} eq "digest" ) {
                $feed .= "?auth=digest";
            }
        }
        push @cleaned, { title => $title, feed => $feed };
    }

    my $vars = {
        'u'             => $u,
        'uids'          => \@cleaned,
        'email_visible' => $u->email_visible($remote)
    };

    my $opml = DW::Template->template_string( 'tools/opml.tt', $vars, { no_sitescheme => 1 } );
    $r->content_type("text/plain");
    $r->print($opml);
    return $r->OK;
}

sub emailmanage_handler {
    my ( $ok, $rv ) = controller( authas => 1 );
    return $rv unless $ok;
    my $remote = $rv->{remote};
    my $r      = DW::Request->get;

    my $dbh    = LJ::get_db_writer();
    my $authas = $r->get_args->{'authas'} || $remote->{'user'};
    my $u      = LJ::get_authas_user($authas);
    return error_ml('error.invalidauth')
        unless $u;

# at the point that the latest email has been on the account for 6 months, *all* previous emails should become removable.
# They should be able to remove all other email addresses at that point if multiple ones are listed,
# so that they can remove anything that might potentially be compromised, leaving only their current/secured email on the account.
# - Anne Zell, 11 december 2008 in LJSUP-3193, based on discussions in lj_core

    my $firstdate = $dbh->selectrow_array(
        qq{
        SELECT MIN(timechange) FROM infohistory
        WHERE userid=? AND what='email'
        AND oldvalue=?
    }, undef, $u->{'userid'}, $u->email_raw
    );

    my $lastdate_email = $dbh->selectrow_array(
        qq{
        SELECT MAX(timechange) FROM infohistory
        WHERE userid=? AND (what='email' OR what='emaildeleted' AND length(other)<=2)
    }, undef, $u->{'userid'}
    );

    my $lastdate_deleted = $dbh->selectrow_array(
        qq{
        SELECT MAX(SUBSTRING(other FROM 3)) FROM infohistory
        WHERE userid=? AND what='emaildeleted'
    }, undef, $u->{'userid'}
    );

    my $lastdate =
        defined $lastdate_deleted
        ? ( $lastdate_email gt $lastdate_deleted ? $lastdate_email : $lastdate_deleted )
        : $lastdate_email;

    # current address was set more, than 6 months ago?
    my $six_month_case = time() - ( str2time($lastdate) // 0 ) > 182 * 24 * 3600;    # half year

    my @deleted;
    if ( $r->did_post && $u->{'status'} eq 'A' ) {
        my $sth =
            $dbh->prepare( "SELECT timechange, oldvalue "
                . "FROM infohistory WHERE userid=? "
                . "AND what='email' ORDER BY timechange" );
        $sth->execute( $u->{'userid'} );
        while ( my ( $time, $email ) = $sth->fetchrow_array ) {
            my $can_del = defined $firstdate && $time gt $firstdate || $six_month_case;
            if ( $can_del && $r->post_args->{"$email-$time"} ) {
                push @deleted,
                    LJ::Lang::ml(
                    '/tools/emailmanage.tt.log.deleted',
                    {
                        'email' => $email,
                        'time'  => $time
                    }
                    );

                $dbh->do(
"UPDATE infohistory SET what='emaildeleted', other=CONCAT(other, ';', timechange), timechange = NOW() "
                        . "WHERE what='email' AND userid=? AND timechange=? AND oldvalue=?",
                    undef, $u->{'userid'}, $time, $email
                );
            }
        }
    }

    my $sth =
        $dbh->prepare( "SELECT timechange, oldvalue FROM infohistory "
            . "WHERE userid=? AND what='email' "
            . "ORDER BY timechange" );
    $sth->execute( $u->{'userid'} );
    my @rows;
    while ( my ( $time, $email ) = $sth->fetchrow_array ) {
        my $can_del = defined $firstdate && $time gt $firstdate || $six_month_case;
        push @rows, { email => $email, time => $time, can_del => $can_del };

    }

    my $vars = {
        'u'           => $u,
        'lastdate'    => $lastdate,
        'authas_html' => $rv->{authas_html},
        'deleted'     => \@deleted,
        'rows'        => \@rows,
        'getextra'    => ( $authas ne $remote->{'user'} ? "?authas=$authas" : '' )
    };
    return DW::Template->render_template( 'tools/emailmanage.tt', $vars );
}

sub tellafriend_handler {
    my ( $ok, $rv ) = controller( authas => 1 );
    return $rv unless $ok;
    my $remote = $rv->{remote};
    my $r      = DW::Request->get;

    my $get_args  = $r->get_args;
    my $post_args = $r->post_args;
    my $scope     = "/tools/tellafriend.tt";

    my $user    = $post_args->{'user'}    || $get_args->{'user'};
    my $journal = $post_args->{'journal'} || $get_args->{'journal'};
    my $itemid  = $post_args->{'itemid'}  || $get_args->{'itemid'};
    my $toemail = $post_args->{'toemail'};

    return error_ml('/tools/tellafriend.tt.error.disabled')
        unless LJ::is_enabled('tellafriend');

    # Get sender's email address
    my $u = LJ::load_userid( $remote->{'userid'} );
    $u->{'emailpref'} = $u->email_raw;
    if ( $u->can_have_email_alias ) {
        $u->{'emailpref'} = $u->site_email_alias;
    }

    my $news_journal = LJ::load_user($LJ::NEWS_JOURNAL);
    my $news_url     = LJ::isu($news_journal) ? $news_journal->journal_base : '';
    my $footer_ml    = LJ::isu($news_journal) ? 'footer.news' : 'footer';

    my $msg_footer = LJ::Lang::ml(
        "$scope.email.body.$footer_ml",
        {
            user          => $u->{user},
            sitename      => $LJ::SITENAME,
            sitenameshort => $LJ::SITENAMESHORT,
            news_url      => $news_url
        }
    );
    my $custom_msg = "\n\n" . LJ::Lang::ml( "$scope.email.body.custom", { user => $u->{user} } );

    my $email_checkbox;
    my $errors = DW::FormErrors->new;

    # Tell a friend form submitted
    if ( $r->did_post && $post_args->{'mode'} eq "mail" ) {
        my @emails = split /,/, $toemail;

        $errors->add( 'toemail', ".error.noemail" ) unless ( scalar @emails );
        my @errors;
        foreach my $em (@emails) {
            LJ::check_email( $em, \@errors, $post_args, \$email_checkbox );
        }

        foreach my $err (@errors) {
            $errors->add( 'toemail', $err );
        }

        # Check for images
        my $custom_body = $post_args->{'body'} // '';
        if ( $custom_body =~ /<(img|image)\s+src/i ) {
            $errors->add( 'body', ".error.forbiddenimages" );
        }

        # Check for external URLs
        foreach ( LJ::get_urls($custom_body) ) {
            if ( $_ !~ m!^https?://([\w-]+\.)?$LJ::DOMAIN(/.*)?$!i ) {
                $errors->add( 'body', ".error.forbiddenurl", { sitename => $LJ::SITENAME } );
            }
        }

        $errors->add( undef, ".error.maximumemails" )
            unless ( $u->rate_log( 'tellafriend', scalar @emails ) );
        $errors->add( 'toemail', ".error.characterlimit" ) if ( length($toemail) > 150 );
        unless ( $errors->exist ) {

            # All valid, go ahead and send

            my $msg_body = $post_args->{'body_start'};
            if ( $custom_body ne '' ) {
                $msg_body .= $custom_msg . "\n-----\n" . $custom_body . "\n-----";
            }
            $msg_body .= $msg_footer;

            LJ::send_mail(
                {
                    'to'       => $toemail,
                    'from'     => $LJ::BOGUS_EMAIL,
                    'fromname' => $u->user . LJ::Lang::ml("$scope.via") . " $LJ::SITENAMESHORT",
                    'charset'  => 'utf-8',
                    'subject'  => $post_args->{'subject'},
                    'body'     => $msg_body,
                    'headers'  => {
                        'Reply-To' => qq{"$u->{user}" <$u->{emailpref}>},
                    }
                }
            );

            my $tolist = $toemail;
            $tolist =~ s/(,\s*)/<br \/>/g;
            $r->add_msg( LJ::Lang::ml("$scope.sentpage.body.mailedlist") . "<br />" . $tolist,
                $r->SUCCESS );
        }
    }

    my ( $subject, $msg );
    $subject = LJ::Lang::ml("$scope.email.subject.noentry");
    $msg     = '';
    if ( defined $itemid && $itemid =~ /^\d+$/ ) {
        my $uj = LJ::load_user($journal);
        return error_ml("$scope.error.unknownjournal") unless $uj;

        my $jitemid = $itemid + 0;
        $jitemid = int( $jitemid / 256 );
        my $entry = LJ::Entry->new( $uj->{'userid'}, jitemid => $jitemid );

        my $entry_subject = $entry->subject_text;
        my $pu            = $entry->poster;
        my $url           = $entry->url;

        my $uisjowner = ( $pu->{'user'} eq $u->{'user'} );
        if ( !$uisjowner && $entry->security ne 'public' ) {
            return error_ml("$scope.email.warning.public");
        }
        if ( $uisjowner && $entry->security eq 'private' ) {
            return error_ml("$scope.email.warning.private");
        }
        if ( $uisjowner && $entry->security eq 'usemask' ) {
            $r->add_msg( LJ::Lang::ml("$scope.email.warning.usemask"), $r->WARNING );
            $msg_footer = "\n\n"
                . LJ::Lang::ml( "$scope.email.usemask.footer",
                { user => $u->{'user'}, siteroot => $LJ::SITEROOT } )
                . "$msg_footer";
        }

        $msg .= LJ::Lang::ml( "$scope.email.body",
            { user => $u->{'user'}, sitenameshort => $LJ::SITENAMESHORT } );
        $msg .= LJ::Lang::ml("$scope.email.sharedentry.title") . " $entry_subject\n"
            if $entry_subject;
        $msg .= LJ::Lang::ml("$scope.email.sharedentry.url") . " $url ";

        # email subject
        $subject =
            $entry_subject
            ? LJ::Lang::ml( "$scope.email.subject.entryhassubject",
            { sitenameshort => $LJ::SITENAMESHORT, subject => $entry_subject } )
            : LJ::Lang::ml( "$scope.email.subject.entrynosubject",
            { sitenameshort => $LJ::SITENAMESHORT } );
    }

    if ( defined $get_args->{'user'} && $get_args->{'user'} =~ /^\w{1,$LJ::USERNAME_MAXLENGTH}$/ ) {
        my $user = $get_args->{'user'};
        my $uj   = LJ::load_user($user);
        my $url  = $uj->journal_base;

        $subject = LJ::Lang::ml("$scope.email.subject.journal");
        if ( $user eq $u->{'user'} ) {
            $msg .= LJ::Lang::ml("$scope.email.body.yourjournal");
            $msg .= "   $url ";
        }
        else {
            $msg .= LJ::Lang::ml("$scope.email.body.otherjournal");
            $msg .= "  $url ";
        }
    }

    my $display_msg        = $msg . $custom_msg;
    my $display_msg_footer = $msg_footer;
    $display_msg        =~ s/\n/<br \/>/gm;
    $display_msg_footer =~ s/\n/<br \/>/gm;

    my $default_formdata = {
        body_start => $msg,
        subject    => $subject,
        user       => $user,
        journal    => $journal,
        itemid     => $itemid
    };

    my $vars = {
        'u'                  => $u,
        'errors'             => $errors,
        'formdata'           => $r->did_post ? $r->post_args : $default_formdata,
        'display_msg'        => $display_msg,
        'display_msg_footer' => $display_msg_footer,
        'email_checkbox'     => $email_checkbox
    };
    return DW::Template->render_template( 'tools/tellafriend.tt', $vars );
}

1;
