#!/usr/bin/perl
#
# 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;

use strict;
use v5.10;
use Log::Log4perl;
my $log = Log::Log4perl->get_logger(__PACKAGE__);

use Encode qw( encode from_to );
use IO::Socket::INET;
use Mail::Address;
use MIME::Base64 qw( encode_base64 );
use MIME::Lite;
use MIME::Words qw( encode_mimeword );
use Text::Wrap ();
use Time::HiRes qw( gettimeofday tv_interval );

use DW::Stats;
use DW::Task::SendEmail;
use LJ::CleanHTML;

# <LJFUNC>
# name: LJ::send_mail
# des: Sends email.  Character set will only be used if message is not ASCII.
# args: opt, async_caller
# des-opt: Hashref of arguments.  Required: to, from, subject, body.
#          Optional: toname, fromname, cc, bcc, charset, wrap, html.
#          All text must be in UTF-8 (without UTF flag, as usual in LJ code).
#          Body and subject are converted to recipient-user mail encoding.
#          Subject line is encoded according to RFC 2047.
#          Warning: opt can be a MIME::Lite ref instead, in which
#          case it is sent as-is.
# </LJFUNC>
sub send_mail {
    my $opt          = shift;
    my $async_caller = shift;

    my $msg = $opt;

    # Record stats about who called us. This is pretty gross, but there are many, many
    # callers so it seems easier to amend this instead of going back and redefining
    # the LJ::send_mail API. For now.
    my ( $package, $filename, $line ) = caller;
    DW::Stats::increment( 'dw.mail.send', 1, [ 'caller:' . "$package/$line" ] );

    # did they pass a MIME::Lite object already?
    unless ( ref $msg eq 'MIME::Lite' ) {

        my $clean_name = sub {
            my ( $name, $email ) = @_;
            return $email unless $name;
            $name =~ s/[\n\t\"<>]//g;
            return $name ? "\"$name\" <$email>" : $email;
        };

        my $body     = $opt->{'wrap'} ? Text::Wrap::wrap( '', '', $opt->{'body'} ) : $opt->{'body'};
        my $subject  = $opt->{'subject'};
        my $fromname = $opt->{'fromname'};

        # if it's not ascii, add a charset header to either what we were explictly told
        # it is (for instance, if the caller transcoded it), or else we assume it's utf-8.
        # Note: explicit us-ascii default charset suggested by RFC2854 sec 6.
        $opt->{'charset'} ||= "utf-8";
        my $charset;
        if (   !LJ::is_ascii($subject)
            || !LJ::is_ascii($body)
            || ( $opt->{html} && !LJ::is_ascii( $opt->{html} ) )
            || !LJ::is_ascii($fromname) )
        {
            $charset = $opt->{'charset'};
        }
        else {
            $charset = 'us-ascii';
        }

        # Don't convert from us-ascii and utf-8 charsets.
        unless ( ( $charset =~ m/us-ascii/i ) || ( $charset =~ m/^utf-8$/i ) ) {
            from_to( $body, "utf-8", $charset );

            # Convert also html-part if we has it.
            if ( $opt->{html} ) {
                from_to( $opt->{html}, "utf-8", $charset );
            }
        }

        from_to( $subject, "utf-8", $charset ) unless $charset =~ m/^utf-8$/i;
        if ( !LJ::is_ascii($subject) ) {
            $subject = MIME::Words::encode_mimeword( $subject, 'B', $charset );
        }

        from_to( $fromname, "utf-8", $charset ) unless $charset =~ m/^utf-8$/i;
        if ( !LJ::is_ascii($fromname) ) {
            $fromname = MIME::Words::encode_mimeword( $fromname, 'B', $charset );
        }
        $fromname = $clean_name->( $fromname, $opt->{'from'} );

        if ( $opt->{html} ) {

            # do multipart, with plain and HTML parts

            $msg = new MIME::Lite(
                'From'    => $fromname,
                'To'      => $clean_name->( $opt->{'toname'}, $opt->{'to'} ),
                'Cc'      => $opt->{cc} || '',
                'Bcc'     => $opt->{bcc} || '',
                'Subject' => $subject,
                'Type'    => 'multipart/alternative'
            );

            # add the plaintext version
            my $plain = $msg->attach(
                'Type'     => 'text/plain',
                'Data'     => "$body\n",
                'Encoding' => 'quoted-printable',
            );
            $plain->attr( "content-type.charset" => $charset );

            # add the html version
            my $html = $msg->attach(
                'Type'     => 'text/html',
                'Data'     => $opt->{html},
                'Encoding' => 'quoted-printable',
            );
            $html->attr( "content-type.charset" => $charset );

        }
        else {
            # no html version, do simple email
            $msg = new MIME::Lite(
                'From'     => $fromname,
                'To'       => $clean_name->( $opt->{'toname'}, $opt->{'to'} ),
                'Cc'       => $opt->{cc} || '',
                'Bcc'      => $opt->{bcc} || '',
                'Subject'  => $subject,
                'Type'     => 'text/plain',
                'Data'     => $body,
                'Encoding' => 'quoted-printable'
            );

            $msg->attr( "content-type.charset" => $charset );
        }

        if ( $opt->{headers} ) {
            while ( my ( $tag, $value ) = each %{ $opt->{headers} } ) {
                $msg->add( $tag, $value );
            }
        }
    }

    # at this point $msg is a MIME::Lite

    # Enqueue in the task system for sending out by a worker
    my $starttime = [ gettimeofday() ];
    my ($env_from) = map { $_->address } Mail::Address->parse( $msg->get('From') );
    my @rcpts;
    push @rcpts, map { $_->address } Mail::Address->parse( $msg->get($_) ) foreach (qw(To Cc Bcc));
    my $host;
    if ( @rcpts == 1 ) {
        $rcpts[0] =~ /(.+)@(.+)$/;
        $host = lc($2) . '@' . lc($1);    # we store it reversed in database
    }
    my $h = DW::TaskQueue->dispatch(
        DW::Task::SendEmail->new(
            {
                env_from   => $env_from,
                rcpts      => \@rcpts,
                data       => $msg->as_string,
                logger_mdc => ref $opt eq 'HASH' ? $opt->{logger_mdc} : undef,
            },
        )
    );
    return $h ? 1 : 0;
}

=head2 C<< LJ::send_formatted_mail( %opts ) >>

Wrapper around LJ::send_mail.

Sends an email in the form of:

[[greeting]],
[[body as plaintext/html]]
[[footer]]

The greeting and footer are generated automatically. The body must not include these.

Required arguments:

=over
=item to - email address
=item from - email address
=item subject
=item body - The body is formatted automatically using Markdown; there's no need to do any text processing yourself.
=back

Optional arguments:
=over
=item greeting_user - the name to greet this user by. If not provided, we don't show the greeting
=item toname - display name
=item fromname - display name
=item cc
=item bcc
=item charset
=back


=cut

sub send_formatted_mail {
    my (%opts) = @_;

    my ( $html_body, $plain_body ) = LJ::format_mail( $opts{body}, $opts{greeting_user} );
    return LJ::send_mail(
        {
            to      => $opts{to},
            from    => $opts{from},
            subject => $opts{subject},

            body => $plain_body,
            html => $html_body,

            toname   => $opts{toname},
            fromname => $opts{fromname},
            cc       => $opts{cc},
            bcc      => $opts{bcc},
            charset  => $opts{charset},
        }
    );
}

=head2 C<< LJ::format_mail( $text )>>

Returns the formatted version of the text as a list of: ( $html_body, $plaintext_body )

Automatically appends greeting and footer.

=cut

sub format_mail {
    my ( $text, $greeting_user ) = @_;

    my $greeting =
        $greeting_user ? LJ::Lang::ml( "email.greeting", { user => $greeting_user } ) : "";
    my $footer = LJ::Lang::ml( "email.footer",
        { sitename => $LJ::SITENAMESHORT, siteroot => $LJ::SITEROOT } );

    $text = "$greeting\n\n$text\n\n$footer";

    # use markdown to format from text to HTML
    my $html = $text;
    my $opts = { editor => 'markdown' };
    LJ::CleanHTML::clean_event( \$html, $opts );

# use plaintext as-is, but look for "[links like these](url)", and change them to "links like these (url)"
    my $plaintext = LJ::strip_html($text);
    $plaintext =~ s/\[(.*?)\]\(/$1 (/g;

    return ( $html, $plaintext );
}
1;
