348 lines
11 KiB
Perl
348 lines
11 KiB
Perl
|
|
#!/usr/bin/perl
|
||
|
|
#
|
||
|
|
# DW::Controller::SendMail
|
||
|
|
#
|
||
|
|
# Admin page for sending emails from site accounts.
|
||
|
|
#
|
||
|
|
# Authors:
|
||
|
|
# Jen Griffin <kareila@livejournal.com>
|
||
|
|
#
|
||
|
|
# Copyright (c) 2012-2015 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::Admin::SendMail;
|
||
|
|
|
||
|
|
use strict;
|
||
|
|
|
||
|
|
use DW::Controller;
|
||
|
|
use DW::Routing;
|
||
|
|
use DW::Template;
|
||
|
|
use DW::Controller::Admin;
|
||
|
|
use DW::FormErrors;
|
||
|
|
|
||
|
|
my @privs = qw(siteadmin:sendmail);
|
||
|
|
|
||
|
|
DW::Controller::Admin->register_admin_page(
|
||
|
|
'/',
|
||
|
|
path => 'sendmail/',
|
||
|
|
ml_scope => '/admin/sendmail/index.tt',
|
||
|
|
privs => \@privs,
|
||
|
|
);
|
||
|
|
|
||
|
|
DW::Routing->register_string( "/admin/sendmail/index", \&index_controller );
|
||
|
|
DW::Routing->register_string( "/admin/sendmail/lookup", \&lookup_controller );
|
||
|
|
DW::Routing->register_string( "/admin/sendmail/message", \&message_controller );
|
||
|
|
|
||
|
|
# FIXME: add this later
|
||
|
|
#DW::Routing->register_string( "/admin/sendmail/forms", \&form_controller );
|
||
|
|
|
||
|
|
# helper function for lookup and message
|
||
|
|
my $enhance_row = sub {
|
||
|
|
my ($row) = @_;
|
||
|
|
|
||
|
|
# link to view full message
|
||
|
|
$row->{msgurl} = LJ::create_url( "/admin/sendmail/message", args => { id => $row->{msgid} } );
|
||
|
|
|
||
|
|
# time_sent needs a display version
|
||
|
|
$row->{time_sent_view} = LJ::time_to_http( $row->{time_sent} );
|
||
|
|
|
||
|
|
# build a request URL
|
||
|
|
if ( $row->{request} ) {
|
||
|
|
$row->{request_url} =
|
||
|
|
LJ::create_url( "/support/see_request", args => { id => $row->{request} } );
|
||
|
|
}
|
||
|
|
|
||
|
|
# sendto might be a uid (if it's an email, no work is needed)
|
||
|
|
if ( $row->{sendto} =~ /^\d+$/ ) {
|
||
|
|
if ( my $u = LJ::load_userid( $row->{sendto} ) ) {
|
||
|
|
$row->{sendto} = $u;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$row->{sendto} = '[unknown user] ' . $row->{sendto};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# sentfrom is definitely a uid
|
||
|
|
if ( my $ru = LJ::load_userid( $row->{remoteid} ) ) {
|
||
|
|
$row->{remote} = $ru;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$row->{remote} = '[unknown user] ' . $row->{remoteid};
|
||
|
|
}
|
||
|
|
|
||
|
|
# add domain name to display site address
|
||
|
|
$row->{account_view} = $row->{account} . "\@$LJ::DOMAIN";
|
||
|
|
|
||
|
|
return $row;
|
||
|
|
};
|
||
|
|
|
||
|
|
sub index_controller {
|
||
|
|
my ( $ok, $rv ) = controller( privcheck => \@privs, form_auth => 1 );
|
||
|
|
return $rv unless $ok;
|
||
|
|
my $remote = $rv->{remote};
|
||
|
|
|
||
|
|
my $scope = sub { return '/admin/sendmail/index.tt' . $_[0] };
|
||
|
|
|
||
|
|
my $r = DW::Request->get;
|
||
|
|
my $errors = DW::FormErrors->new;
|
||
|
|
|
||
|
|
# form processing
|
||
|
|
if ( $r->did_post ) {
|
||
|
|
my $args = $r->post_args;
|
||
|
|
|
||
|
|
my $account = LJ::trim( $args->{account} );
|
||
|
|
my $sendto = LJ::trim( $args->{sendto} );
|
||
|
|
my $message = LJ::trim( $args->{message} );
|
||
|
|
my $subject = LJ::trim( $args->{subject} );
|
||
|
|
my $support_req = LJ::trim( $args->{request} );
|
||
|
|
my $teamnotes = LJ::trim( $args->{notes} );
|
||
|
|
my $reqsubj = $args->{reqsubj} ? 1 : 0;
|
||
|
|
|
||
|
|
if ($account) {
|
||
|
|
$errors->add( "account", ".error.badacct" )
|
||
|
|
unless exists $LJ::SENDMAIL_ACCOUNTS{$account};
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$errors->add( "account", ".error.noacct" );
|
||
|
|
}
|
||
|
|
|
||
|
|
$errors->add( "subject", ".error.nosubj" ) unless $subject;
|
||
|
|
$errors->add( "message", ".error.nomsg" ) unless $message;
|
||
|
|
|
||
|
|
if ($support_req) {
|
||
|
|
if ( $support_req =~ /^\d+$/ ) {
|
||
|
|
$subject = "[\#$support_req] $subject" if $reqsubj;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$errors->add( "request", ".error.badreq" );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
my $u;
|
||
|
|
|
||
|
|
if ($sendto) {
|
||
|
|
if ( $sendto =~ /,/ ) { # multiple recipients
|
||
|
|
$errors->add( "sendto", ".error.nomulti" );
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
# make sure we're sending to either a valid user or something
|
||
|
|
# that looks reasonably like an email address.
|
||
|
|
if ( $sendto !~ /^[^@]+@[^.]+\./ ) {
|
||
|
|
|
||
|
|
# doesn't look like an email address; do a username lookup
|
||
|
|
$u = LJ::load_user($sendto);
|
||
|
|
$errors->add( "sendto", ".error.badrcpt" ) unless defined $u;
|
||
|
|
$sendto = $u->id; # log userid instead of username
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else { # no $sendto
|
||
|
|
$errors->add( "sendto", ".error.norcpt" );
|
||
|
|
}
|
||
|
|
|
||
|
|
# at this point, we should have good form data; the form can still
|
||
|
|
# have errors below, but they aren't the fault of the user input
|
||
|
|
|
||
|
|
unless ( $errors->exist ) {
|
||
|
|
|
||
|
|
# now that we have the data, send the message.
|
||
|
|
# 1. insert data into siteadmin_email_history table
|
||
|
|
my $msgid = LJ::alloc_global_counter('N');
|
||
|
|
my $dbh = LJ::get_db_writer();
|
||
|
|
$dbh->do(
|
||
|
|
"INSERT INTO siteadmin_email_history (msgid, remoteid,"
|
||
|
|
. " time_sent, account, sendto, subject, request, message, "
|
||
|
|
. " notes) VALUES (?,?,?,?,?,?,?,?,?)",
|
||
|
|
undef,
|
||
|
|
$msgid,
|
||
|
|
$remote->id,
|
||
|
|
time,
|
||
|
|
$account,
|
||
|
|
$sendto,
|
||
|
|
$subject,
|
||
|
|
$support_req,
|
||
|
|
$message,
|
||
|
|
$teamnotes
|
||
|
|
) or return error_ml( $scope->('.error.sendfailed') );
|
||
|
|
|
||
|
|
# keeping this as error_ml; if we get a DB error things are FUBAR
|
||
|
|
|
||
|
|
# 2. construct the message and send it to the user(s)
|
||
|
|
# (this block adapted from bin/worker/paidstatus)
|
||
|
|
my $msg = {
|
||
|
|
from => "$account\@$LJ::DOMAIN",
|
||
|
|
fromname => $LJ::SITENAME,
|
||
|
|
subject => $subject,
|
||
|
|
body => $message,
|
||
|
|
};
|
||
|
|
my $sent = 0;
|
||
|
|
|
||
|
|
my $send = sub { LJ::send_mail($msg); $sent = 1; };
|
||
|
|
|
||
|
|
if ( $u && $u->is_community ) {
|
||
|
|
|
||
|
|
# send an email to every maintainer
|
||
|
|
my $maintus = LJ::load_userids( $u->maintainer_userids );
|
||
|
|
foreach my $maintu ( values %$maintus ) {
|
||
|
|
$msg->{to} = $maintu->email_raw;
|
||
|
|
$send->() if $msg->{to};
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$msg->{to} = $u ? $u->email_raw : $sendto;
|
||
|
|
$send->() if $msg->{to};
|
||
|
|
}
|
||
|
|
|
||
|
|
# 3. update userlog and return success message
|
||
|
|
if ($sent) {
|
||
|
|
$remote->log_event(
|
||
|
|
'siteadmin_email',
|
||
|
|
{
|
||
|
|
account => $account,
|
||
|
|
msgid => $msgid
|
||
|
|
}
|
||
|
|
);
|
||
|
|
return success_ml(
|
||
|
|
$scope->('.success.msgtext'),
|
||
|
|
undef,
|
||
|
|
[
|
||
|
|
{
|
||
|
|
text => LJ::Lang::ml( $scope->('.success.linktext.a') ),
|
||
|
|
url => '/admin/sendmail'
|
||
|
|
}
|
||
|
|
]
|
||
|
|
);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$errors->add( "sendto", ".error.nouseremail" );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# end form processing
|
||
|
|
|
||
|
|
# Construct data for dropdown of available email addresses;
|
||
|
|
# the user should have at least one of these for this page to be useful.
|
||
|
|
# If the user somehow has sendmail priv but no relevant account priv,
|
||
|
|
# we print an error in the template in that case.
|
||
|
|
my @account_menu = ( "", LJ::Lang::ml( $scope->('.select.account.choose') ) );
|
||
|
|
|
||
|
|
foreach my $account ( sort keys %LJ::SENDMAIL_ACCOUNTS ) {
|
||
|
|
my $priv = $LJ::SENDMAIL_ACCOUNTS{$account};
|
||
|
|
push @account_menu, ( $account, "$account\@$LJ::DOMAIN" )
|
||
|
|
if $remote->has_priv($priv);
|
||
|
|
}
|
||
|
|
|
||
|
|
$rv->{has_menu} = ( @account_menu > 2 );
|
||
|
|
$rv->{account_menu} = \@account_menu;
|
||
|
|
|
||
|
|
$rv->{errors} = $errors;
|
||
|
|
$rv->{formdata} = $r->post_args;
|
||
|
|
|
||
|
|
return DW::Template->render_template( 'admin/sendmail/index.tt', $rv );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub lookup_controller {
|
||
|
|
my ( $ok, $rv ) = controller( privcheck => \@privs, form_auth => 1 );
|
||
|
|
return $rv unless $ok;
|
||
|
|
my $remote = $rv->{remote};
|
||
|
|
|
||
|
|
my $scope = sub { return '/admin/sendmail/lookup.tt' . $_[0] };
|
||
|
|
|
||
|
|
my $r = DW::Request->get;
|
||
|
|
my $errors = DW::FormErrors->new;
|
||
|
|
|
||
|
|
if ( $r->did_post ) {
|
||
|
|
my $args = $r->post_args;
|
||
|
|
|
||
|
|
my $account = LJ::trim( $args->{account} );
|
||
|
|
|
||
|
|
if ($account) {
|
||
|
|
if ( my $priv = $LJ::SENDMAIL_ACCOUNTS{$account} ) {
|
||
|
|
$errors->add( "account", ".error.nopriv", { priv => $priv } )
|
||
|
|
unless $remote->has_priv($priv);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$errors->add( "account", ".error.badacct" );
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$errors->add( "account", ".error.noacct" );
|
||
|
|
}
|
||
|
|
|
||
|
|
unless ( $errors->exist ) {
|
||
|
|
my $dbr = LJ::get_db_reader();
|
||
|
|
my $rows = $dbr->selectall_hashref(
|
||
|
|
"SELECT msgid, time_sent, sendto, subject, request"
|
||
|
|
. " FROM siteadmin_email_history WHERE account=?",
|
||
|
|
'msgid', undef, $account
|
||
|
|
);
|
||
|
|
die $dbr->errstr if $dbr->err;
|
||
|
|
|
||
|
|
my @data = map { $enhance_row->($_) } values %$rows;
|
||
|
|
|
||
|
|
$rv->{rows} = [ sort { $b->{time_sent} <=> $a->{time_sent} } @data ];
|
||
|
|
$rv->{account} = $account;
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
# Construct data for dropdown of available email addresses
|
||
|
|
my @account_menu = ( "", LJ::Lang::ml( $scope->('.select.account.choose') ) );
|
||
|
|
|
||
|
|
foreach my $account ( sort keys %LJ::SENDMAIL_ACCOUNTS ) {
|
||
|
|
my $priv = $LJ::SENDMAIL_ACCOUNTS{$account};
|
||
|
|
push @account_menu, ( $account, "$account\@$LJ::DOMAIN" )
|
||
|
|
if $remote->has_priv($priv);
|
||
|
|
}
|
||
|
|
|
||
|
|
$rv->{has_menu} = ( @account_menu > 2 );
|
||
|
|
$rv->{account_menu} = \@account_menu;
|
||
|
|
|
||
|
|
$rv->{errors} = $errors;
|
||
|
|
$rv->{formdata} = $r->post_args;
|
||
|
|
|
||
|
|
return DW::Template->render_template( 'admin/sendmail/lookup.tt', $rv );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub message_controller {
|
||
|
|
my ( $ok, $rv ) = controller( privcheck => \@privs );
|
||
|
|
return $rv unless $ok;
|
||
|
|
my $remote = $rv->{remote};
|
||
|
|
|
||
|
|
my $scope = sub { return '/admin/sendmail/lookup.tt' . $_[0] };
|
||
|
|
|
||
|
|
my $r = DW::Request->get;
|
||
|
|
my $args = $r->get_args;
|
||
|
|
my $msgid = LJ::trim( $args->{id} );
|
||
|
|
|
||
|
|
return $r->redirect("/admin/sendmail/lookup")
|
||
|
|
unless $msgid && $msgid =~ /^\d+$/;
|
||
|
|
|
||
|
|
my $dbr = LJ::get_db_reader();
|
||
|
|
my $row = $dbr->selectrow_hashref( "SELECT * FROM siteadmin_email_history WHERE msgid=?",
|
||
|
|
undef, $msgid );
|
||
|
|
die $dbr->errstr if $dbr->err;
|
||
|
|
|
||
|
|
return error_ml( $scope->('.error.nomsg') ) unless $row;
|
||
|
|
|
||
|
|
my $priv = $LJ::SENDMAIL_ACCOUNTS{ $row->{account} };
|
||
|
|
|
||
|
|
if ( $priv && $remote->has_priv($priv) ) {
|
||
|
|
$rv->{row} = $enhance_row->($row);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
return error_ml( $scope->('.error.nopriv'), { priv => $priv } );
|
||
|
|
}
|
||
|
|
|
||
|
|
return DW::Template->render_template( 'admin/sendmail/message.tt', $rv );
|
||
|
|
}
|
||
|
|
|
||
|
|
1;
|