#!/usr/bin/perl # # DW::Controller::SendMail # # Admin page for sending emails from site accounts. # # Authors: # Jen Griffin # # 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;