# $Id: Sendmail.pm,v 1.15 2005/04/13 16:04:21 kjetil Exp $

package AxKit::XSP::Sendmail;
use strict;
use Apache::AxKit::Language::XSP;
use Mail::Sendmail;
use Email::Valid;
use Carp;
use Apache::AxKit::CharsetConv;

use vars qw/@ISA $NS $VERSION $ForwardXSPExpr $TRIM_FIELD/;

@ISA = ('Apache::AxKit::Language::XSP');
$NS = 'http://axkit.org/NS/xsp/sendmail/v1';

$VERSION = "1.5";

## Taglib subs

# send mail
sub send_mail {
    my ($document, $parent, $mailer_args) = @_;
    my $address_errors;

    foreach my $addr_type ('To', 'Cc', 'Bcc') {
        if ($mailer_args->{$addr_type}) {
            foreach my $addr (@{$mailer_args->{$addr_type}}) {
                next if Email::Valid->address($addr);
                $address_errors .=  "Address $addr in '$addr_type' element failed $Email::Valid::Details check. ";
            $mailer_args->{$addr_type}  = join (', ', @{$mailer_args->{$addr_type}});

    # we want a bad "from" header to be caught as a user error so we'll trap it here.
    $mailer_args->{From} ||= $Mail::Sendmail::mailcfg{from};

    unless ( Email::Valid->address($mailer_args->{From}) ) { 
        $address_errors .= "Address '$mailer_args->{From}' in 'From' element failed $Email::Valid::Details check. ";

    # set the content-type
    $mailer_args->{'Content-Type'} = ($mailer_args->{'Content-Type'})? $mailer_args->{'Content-Type'} : 'text/plain';
    $mailer_args->{'Content-Type'} .= '; charset=';
    $mailer_args->{'Content-Type'} .= ($mailer_args->{'charset'})?      $mailer_args->{'charset'} : 'utf-8';

    # munge the text if it needs to be
    if ($mailer_args->{'charset'} and lc($mailer_args->{'charset'}) ne 'utf-8') {
        my $conv = Apache::AxKit::CharsetConv->new('utf-8',$mailer_args->{'charset'})
                or croak "No such charset: $mailer_args->{'charset'}";
        $mailer_args->{'message'} = $conv->convert($mailer_args->{'message'});

    if ($address_errors) {
        croak "Invalid Email Address(es): $address_errors";

    # all addresses okay? if so, send.
    sendmail( %{$mailer_args} ) || croak $Mail::Sendmail::error;

## Parser subs
sub parse_start {
    my ($e, $tag, %attribs) = @_; 
    #warn "Checking: $tag\n";
    # check for trimming
    $TRIM_FIELD = ($attribs{trim} eq 'no' ? 0 : 1);

    if ($tag eq 'send-mail') {
        return qq| {# start mail code\n | .
                q| my (%mail_args, @to_addrs, @cc_addrs, @bcc_addrs);| . qq|\n|;
    elsif ($tag eq 'to') {
        return q| push (@to_addrs, ''|;
    elsif ($tag eq 'cc') {
        return q| push (@cc_addrs, ''|;
    elsif ($tag eq 'bcc') {
        return q| push (@bcc_addrs, ''|;
    elsif ($tag eq 'content-type') {
        return q| $mail_args{'Content-Type'} = ''|;
    elsif ($tag eq 'content-transfer-encoding') {
        return q| $mail_args{'Content-Transfer-Encoding'} = ''|;
    elsif ($tag eq 'charset') {
        return q| $mail_args{'charset'} = ''|;
    elsif ($tag =~ /^(subject|message|from|body)$/) {
        $tag = "From" if $tag eq 'from';
        $tag = "message" if $tag eq 'body';
        return qq| \$mail_args{'$tag'} = "" |;
    elsif ($tag eq 'smtphost') {
        return q| $mail_args{'smtp'} = "" |;
    elsif ($tag eq 'header') {
        return qq| \$mail_args{'$attribs{name}'} = ''|;
    else {
        die "Unknown sendmail tag: $tag";

sub parse_char {
    my ($e, $text) = @_;
    my $element_name = $e->current_element();

    if ($element_name ne 'body' and $TRIM_FIELD) {
	$text =~ s/^\s*//;
	$text =~ s/\s*$//;

    return '' unless $text;

    $text =~ s/\|/\\\|/g;
    $text =~ s/\\$/\\\\/gsm;
    return " . q|$text| ";

sub parse_end {
    my ($e, $tag) = @_;

    $TRIM_FIELD = 1;
    if ($tag eq 'send-mail') {
        return <<'EOF';
    $document, $parent,
        To => \@to_addrs, 
        Cc => \@cc_addrs, 
        Bcc => \@bcc_addrs,
} # end mail code
    elsif ($tag =~ /to|bcc|cc/) {
        return ");\n";
    return ";";

sub parse_comment {
    # compat only

sub parse_final {
   # compat only


=head1 NAME

AxKit::XSP::Sendmail - Simple SMTP mailer tag library for AxKit eXtensible Server Pages.


Add the sendmail: namespace to your XSP C<<xsp:page>> tag:


And add this taglib to AxKit (via httpd.conf or .htaccess):

    AxAddXSPTaglib AxKit::XSP::Sendmail


The XSP sendmail: taglib adds a simple SMTP mailer to XSP via Milivoj
Ivkovic's platform-neutral Mail::Sendmail module. In addition, all
email addresses are validated before sending using Maurice Aubrey's
Email::Valid package. This taglib is identical to the Cocoon taglib
of the same name, albeit in a different namespace..

=head1 Tag Reference

=head2 C<E<lt>sendmail:send-mailE<gt>>

This is the required 'wrapper' element for the sendmail taglib branch.

=head2 C<E<lt>sendmail:smtphostE<gt>>

The this element sets the outgoing SMTP server for the current message.
If omitted, the default set in L<Mail::Sendmail>'s %mailcfg hash will be
used instead. 

=head2 C<E<lt>sendmail:fromE<gt>>

Defines the 'From' field in the outgoing message. If omitted, this
field defaults to value set in L<Mail::Sendmail>'s %mailcfg hash. Run
C<perldoc Mall:Sendmail> for more detail.

=head2 C<E<lt>sendmail:toE<gt>>

Defines a 'To' field in the outgoing message. Multiple instances are
allowed. By default this taglib will remove leading and trailing
spaces from the value C<E<lt>sendmail:toE<gt>> contains. If you need
to turn this off, simply set the C<trim> attribute to 'no'. The same
can be done for all header fields.

=head2 C<E<lt>sendmail:ccE<gt>>

Defines a 'Cc' field in the outgoing message. Multiple instances are

=head2 C<E<lt>sendmail:bccE<gt>>

Defines a 'Bcc' field in the outgoing message. Multiple instances are

=head2 C<E<lt>sendmail:subjectE<gt>>

Defines the subject of the message.

=head2 C<E<lt>sendmail:content-typeE<gt>>

Defines the content-type of the body of the message (default: text/plain).

=head2 C<E<lt>sendmail:content-transfer-encodingE<gt>>

Defines the content-transfer-encoding of the body of the message. The
default depends on whether you have MIME::QuotedPrint available or not.
If you do, it defaults to 'quoted-printable', and if you don't to '8bit';

=head2 C<E<lt>sendmail:charsetE<gt>>

Defines the charset of the body of the message (default: utf-8). Your
system's iconv implementation needs to support converting from utf-8
to that character set otherwise sending email will fail.

=head2 C<E<lt>sendmail:headerE<gt>>

Allows you to add headers to the outgoing mail with the name specified
in the C<name> attribute.

=head2 C<E<lt>sendmail:bodyE<gt>>

Defines the body of the outgoing message.

=head2 C<E<lt>sendmail:messageE<gt>>

This tag is interchangable with C<E<lt>sendmail:bodyE<gt>>.

=head1 EXAMPLE

  my $mail_message = 'I\'m a victim of circumstance!';


=head1 ERRORS

When sending email fails, or an address is invalid, this taglib will
throw an exception, which you can catch with the AxKit exceptions

=head1 AUTHOR

Kip Hampton, khampton@totalcinema.com

Kjetil Kjernsmo, kjetilk@cpan.org has taken over maintainership of
this module as of 1.41.


Copyright (c) 2001 Kip Hampton. All rights reserved. This program is
free software; you can redistribute it and/or modify it under the same
terms as Perl itself.

=head1 SEE ALSO

L<AxKit>, L<Mail::Sendmail>, L<Email::Valid>