# ================================================================== # Gossamer Forum - Advanced web community # # Website : http://gossamer-threads.com/ # Support : http://gossamer-threads.com/scripts/support/ # Revision : $Id: MassMailer.pm,v 1.8 2005/09/19 19:44:26 jagerman Exp $ # # Copyright (c) 2003 Gossamer Threads Inc. All Rights Reserved. # Redistribution in part or in whole strictly prohibited. Please # see LICENSE file for full details. # ================================================================== package GForum::MassMailer; # ================================================================== # This package contains some builtin functions useful in your templates. # use GForum qw/$DB $IN $CFG/; use GT::Mail::BulkMail qw/$VALID_HOST/; use GT::CGI; use strict; use vars qw(%ACTIONS $NEW_TEMPLATE @MONTH @DAY @WEEKDAY); $NEW_TEMPLATE = '(New template)'; @MONTH = qw/January February March April May June July August September October November December/; @DAY = qw/blank 1st 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th 31st/; @WEEKDAY = qw/Sunday Monday Tuesday Wednesday Thursday Friday Saturday/; # (See _save_template for arguments) sub add_template ($$$$$$;$) { _save_template(@_); } # (See _save_template for arguments) sub save_template ($$$$$$;$) { _save_template(@_); } # Deletes a template based on its name sub del_template ($) { $DB->table('EmailTemplate')->delete(shift); } # Updates (or creates) a row for the template given in EmailTemplate # Takes up to 6 args: # name, from, fromname, subject, message, message format ('text' or 'html'), # link template (for newsletter template) sub _save_template { my ($name,$from,$fromname,$subject,$message,$format) = @_; for ($name,$from,$fromname,$subject,$message) { defined $_ or $_ = ""; } $format = 'text' unless defined $format and $format eq 'html'; my $table = $DB->table('EmailTemplate'); if ($table->count({ email_tpl_name => $name })) { $table->update( { email_tpl_from => $from, email_tpl_from_name => $fromname, email_tpl_subject => $subject, email_tpl_body => $message, email_tpl_format => $format }, { email_tpl_name => $name } ); } else { $table->insert( { email_tpl_name => $name, email_tpl_from => $from, email_tpl_from_name => $fromname, email_tpl_subject => $subject, email_tpl_body => $message, email_tpl_format => $format } ); } } # Returns a template with the name given by the provided argument. # Returns a 5 element list of: from, fromname, subject, message, message format ('text' or 'html') sub get_template ($) { return @{$DB->table('EmailTemplate')->get(shift, 'ARRAY') or [('') x 5, 'text']}[1..5]; } sub template_names () { my @return = map $_->[0], @{$DB->table('EmailTemplate')->select('email_tpl_name')->fetchall_arrayref}; @return; } # Returns an HTML option string ("...") based on the provided arguments. # Takes 1 or 2 arguments: # - an array reference of options # - a "selected" option. Any matching option will be given a "selected" tag. sub make_opts ($;$) { my $opts = shift; my $selected = shift || ""; return join "\n", map { my $escaped = GT::CGI::html_escape($_) || ''; qq|| } @$opts; } # Returns a string such as "Saturday, January 1st, 2000, 00:00:00" for a integral time such as 946713600 sub make_date_string ($) { my $date = shift; my @lt = localtime($date); return "$WEEKDAY[$lt[6]], $MONTH[$lt[4]] $DAY[$lt[3]], ". ($lt[5]+1900).", ".sprintf("%02d:%02d:%02d",@lt[2,1,0]); } # ========================================= # Actions # ========================================= # # These subroutines (in the GForum::MassMailer::ACTIONS namespace) are called directly when a CGI # option of: action=option is made. # # The default frames page sub GForum::MassMailer::ACTIONS::frames { GForum::page( "email_frames.html", { version => $CFG->{version}, main => "admin.cgi?action=main", contents => "admin.cgi?action=contents" } ); } # A generic error page sub GForum::MassMailer::ACTIONS::error { GForum::page( "email_error.html", { error => "@_" } ); } # The main body page (to be displayed by GForum::MassMailer::ACTIONS::frames) sub GForum::MassMailer::ACTIONS::main { GForum::page( "email_default.html", { } ); } # The contents page (to be displayed by GForum::MassMailer::ACTIONS::frames) sub GForum::MassMailer::ACTIONS::contents { GForum::page( "email_contents.html", { } ); } # * -> "Load" (template) sub GForum::MassMailer::ACTIONS::Load { my $name = $IN->param('name'); $GForum::MassMailer::ACTIONS::{$IN->param('previous')}->($name,get_template($name)) if exists $GForum::MassMailer::ACTIONS::{$IN->param('previous')} and ref *{$GForum::MassMailer::ACTIONS::{$IN->param('previous')}}{CODE} eq 'CODE'; } # * -> "Save" (template) sub GForum::MassMailer::ACTIONS::Save { my $name = $IN->param('name'); return $GForum::MassMailer::ACTIONS::{SaveAs}->() if $name eq $NEW_TEMPLATE; my $from = $IN->param('from'); my $fromname = $IN->param('fromname'); my $subject = $IN->param('subject'); my $message = $IN->param('message'); my $format = $IN->param('messageformat'); save_template($name,$from,$fromname,$subject,$message,$format); $GForum::MassMailer::ACTIONS::{$IN->param('previous')}->($name,(get_template($name))[0..4],"Template saved successfully") if exists $GForum::MassMailer::ACTIONS::{$IN->param('previous')} and ref *{$GForum::MassMailer::ACTIONS::{$IN->param('previous')}}{CODE} eq 'CODE'; } # * -> "Save as..." (template) sub GForum::MassMailer::ACTIONS::SaveAs { $IN->delete('name'); my %substitutions = ( notes => "@_", from => defined $IN->param('from') ? scalar $IN->param('from') : "", fromname => defined $IN->param('fromname') ? scalar $IN->param('fromname') : "", subject => defined $IN->param('subject') ? scalar $IN->param('subject') : "", message => defined $IN->param('message') ? scalar $IN->param('message') : "", messageformat => defined $IN->param('messageformat') ? scalar $IN->param('messageformat') : "text", previous => defined $IN->param('previous') ? scalar $IN->param('previous') : "", hidden_fields => "" ); $IN->delete('from'); $IN->delete('fromname'); $IN->delete('subject'); $IN->delete('message'); $IN->delete('messageformat'); $IN->delete('action'); chomp($substitutions{from}); if ($substitutions{from} !~ /^[\x21-\x7e]+\@$VALID_HOST(?!\n)$/) { $GForum::MassMailer::ACTIONS::{$IN->param('previous')}->(undef,"",@substitutions{qw/fromname subject message messageformat/},"Invalid e-mail address entered. Correct it before saving the template") if exists $GForum::MassMailer::ACTIONS::{$IN->param('previous')} and ref *{$GForum::MassMailer::ACTIONS::{$IN->param('previous')}}{CODE} eq 'CODE'; return; } if ($IN->param('previous') and substr($IN->param('previous'),0,8) eq 'selected') { $substitutions{hidden_fields} = join "\n", map '', $IN->param; } for (keys %substitutions) { next if $_ eq 'hidden_fields'; $substitutions{$_} =~ s/&/&/g; $substitutions{$_} =~ s/"/"/g; $substitutions{$_} =~ s/>/>/g; $substitutions{$_} =~ s/ (Template) "Save as..." or "Save" with "(New template)" selected -> "Add Template" sub GForum::MassMailer::ACTIONS::email_newtemp { my $new_name = $IN->param('name'); my $previous = $IN->param('previous'); $new_name =~ s/^\s+//; $new_name =~ s/\s+$//; if ($new_name !~ /\S/) { GForum::MassMailer::ACTIONS::SaveAs(qq|Bad input: Invalid template name ($new_name)!|); } else { my $from = $IN->param('from'); chomp($from); add_template($new_name,($from =~ /^[\x20-\x7e]+\@$VALID_HOST$/ ? $from : ''), $IN->param('fromname'),$IN->param('subject'),$IN->param('message'),$IN->param('messageformat')); $GForum::MassMailer::ACTIONS::{$previous}->($new_name,(get_template($new_name))[0..4],"Template saved successfully"); } } # * -> "Delete" (template) sub GForum::MassMailer::ACTIONS::Delete { del_template($IN->param('name')) if $IN->param('name') and $IN->param('name') ne $NEW_TEMPLATE; $GForum::MassMailer::ACTIONS::{$IN->param('previous')}->() if exists $GForum::MassMailer::ACTIONS::{$IN->param('previous')} and ref *{$GForum::MassMailer::ACTIONS::{$IN->param('previous')}}{CODE} eq 'CODE'; } # "All Users" -> "Send" sub GForum::MassMailer::ACTIONS::Send { my $to = $IN->param('emailsto'); return unless $to and $to eq 'EVERYONE'; unless ($IN->param('from') and $IN->param('from') =~ /^[\x21-\x7e]+\@$VALID_HOST(?!\n)$/) { my %substitutions = ( notes => "@_", from => defined $IN->param('from') ? scalar $IN->param('from') : "", fromname => defined $IN->param('fromname') ? scalar $IN->param('fromname') : "", subject => defined $IN->param('subject') ? scalar $IN->param('subject') : "", message => defined $IN->param('message') ? scalar $IN->param('message') : "", messageformat => defined $IN->param('messageformat') ? scalar $IN->param('messageformat') : "text", previous => defined $IN->param('previous') ? scalar $IN->param('previous') : "", hidden_fields => "" ); $IN->delete('from'); $IN->delete('fromname'); $IN->delete('subject'); $IN->delete('message'); $IN->delete('messageformat'); $IN->delete('action'); $GForum::MassMailer::ACTIONS::{$IN->param('previous')}->(undef,"",@substitutions{qw/fromname subject message messageformat/},"Invalid from e-mail address entered. You must correct it before sending the e-mails") if exists $GForum::MassMailer::ACTIONS::{$IN->param('previous')} and ref *{$GForum::MassMailer::ACTIONS::{$IN->param('previous')}}{CODE} eq 'CODE'; return; } my $sth = $DB->table('User')->select('user_id'); my $mailingnum = $DB->table('MailingIndex')->insert({ mailing_from => $IN->param('from'), mailing_name => $IN->param('fromname'), mailing_subject => $IN->param('subject'), mailing_message => $IN->param('message'), mailing_format => $IN->param('messageformat'), })->insert_id; my $emailtable = $DB->table('EmailRecipient'); while (my $uid = $sth->fetchrow_arrayref) { $emailtable->insert({ mailing_id_fk => $mailingnum, user_id_fk => $uid->[0], recipient_sent => 0 }); } $GForum::MassMailer::ACTIONS::{mailings}->('Mailing queued for sending. To send, select "Start mailing" below beside the mailing you just queued.'); } # * -> "x recipients" sub GForum::MassMailer::ACTIONS::list_addresses { my $to = $IN->param('emailsto'); return unless defined $to; if ($to eq 'EVERYONE') { GForum::page( "email_list.html", { addresses => join("
\n", $DB->table('User')->select('user_email')->fetchall_list) } ); } elsif ($to eq 'SELECTEDUSERS') { my $table = $DB->table('User'); $IN->delete("action"); my $sth = $table->query_sth($IN); my @emails; while ($_ = $sth->fetchrow_hashref) { push @emails, $_->{user_email} if $_->{user_email}; } GForum::page( "email_list.html", { addresses => join("
\n", @emails) } ); } else { # It's just a mailing ID that has been passed my @emails; my $sth = $DB->table('EmailRecipient', 'User')->select(user_email => { mailing_id_fk => $to }); while ($_ = $sth->fetchrow_arrayref) { push @emails, $_->[0] if $_->[0]; } GForum::page( "email_list.html", { addresses => join("
\n", @emails) } ); } } # "All Users" sub GForum::MassMailer::ACTIONS::email_everyone { my ($selected,$from,$fromname,$subject,$message,$format,$error) = splice @_,0,7; GForum::page( "email_everyone.html", { number => $DB->table('User')->count(), templates => make_opts([$NEW_TEMPLATE,template_names()],$selected), from => GT::CGI::html_escape($from) || '', fromname => GT::CGI::html_escape($fromname) || '', subject => GT::CGI::html_escape($subject) || '', message => GT::CGI::html_escape($message) || '', messageformat => make_opts([qw/text html/],$format), error => $error || "" } ); } # "Selected Users" sub GForum::MassMailer::ACTIONS::selected_users { $IN->param('do' => 'massmailer_search_selected_users'); # having a do with a value containing "search" will hide the user_do_after_post field, which will screw up searches. my $html = $DB->html($DB->table('User'), $IN); $IN->delete("do"); $html->{code}{ReceiveMail} = sub { qq{ReceiveMailYes\n \n} }; GForum::page( "email_selected_users.html", { message => "@_", form => $html->form({ mode => 'search_form', search_opts => 1 }), } ); } # "Selected Users" -> "Search" sub GForum::MassMailer::ACTIONS::selected_users_search { my ($selected,$from,$fromname,$subject,$message,$format,$error) = splice @_,0,7; $IN->delete('action'); $IN->delete('name'); $IN->delete('from'); $IN->delete('fromname'); $IN->delete('subject'); $IN->delete('message'); $IN->delete('messageformat'); my $sth = $DB->table('User')->query_sth($IN); my $list_addrs_url = $IN->url(remove_empty => 1); $list_addrs_url .= "&action=list_addresses&emailsto=SELECTEDUSERS"; my $count = $sth->rows; return $GForum::MassMailer::ACTIONS::{selected_users}->("No matches found") unless $count; my $hidden_fields = ""; for ($IN->param) { my $name = GT::CGI::html_escape($_); my @val = $IN->param($_); for my $val (@val) { $hidden_fields .= qq{\n}; } } GForum::page( "email_selected_users_mail.html", { number => $count, address_list_url => $list_addrs_url, hidden_fields => $hidden_fields, templates => make_opts([$NEW_TEMPLATE,template_names()],$selected), from => $from, fromname => $fromname, subject => $subject, message => $message, messageformat => make_opts([qw/text html/],$format), error => $error || "" } ); } # "Selected Users" -> "Search" -> "Send" sub GForum::MassMailer::ACTIONS::selected_users_send { my $sth = $DB->table('User')->query_sth($IN); my $emailtable = $DB->table('EmailRecipient'); unless ($IN->param('from') and $IN->param('from') =~ /^[\x21-\x7e]+\@$VALID_HOST\Z/) { my %substitutions = ( notes => "@_", from => defined $IN->param('from') ? scalar $IN->param('from') : "", fromname => defined $IN->param('fromname') ? scalar $IN->param('fromname') : "", subject => defined $IN->param('subject') ? scalar $IN->param('subject') : "", message => defined $IN->param('message') ? scalar $IN->param('message') : "", messageformat => defined $IN->param('messageformat') ? scalar $IN->param('messageformat') : "text", previous => defined $IN->param('previous') ? scalar $IN->param('previous') : "", hidden_fields => "" ); $IN->delete('from'); $IN->delete('fromname'); $IN->delete('subject'); $IN->delete('message'); $IN->delete('messageformat'); $IN->delete('action'); $GForum::MassMailer::ACTIONS::{$IN->param('previous')}->(undef,"",@substitutions{qw/fromname subject message messageformat/},"Invalid from e-mail address entered. You must correct it before sending the e-mails") if exists $GForum::MassMailer::ACTIONS::{$IN->param('previous')} and ref *{$GForum::MassMailer::ACTIONS::{$IN->param('previous')}}{CODE} eq 'CODE'; return; } my $mailingnum = $DB->table('MailingIndex')->insert( mailing_from => $IN->param('from'), mailing_name => $IN->param('fromname'), mailing_subject => $IN->param('subject'), mailing_message => $IN->param('message'), mailing_format => $IN->param('messageformat') )->insert_id; while (my $uid = $sth->fetchrow_arrayref) { $emailtable->insert({ mailing_id_fk => $mailingnum, user_id_fk => $uid->[0], recipient_sent => 0 }); } $GForum::MassMailer::ACTIONS::{mailings}->('Mailing queued for sending. To send, select "Start mailing" below beside the mailing you just queued.'); } # "View Mailings" (Also shown immediately after queuing a message) sub GForum::MassMailer::ACTIONS::mailings { my $error = shift; my @mailings; my $sth = $DB->table('MailingIndex')->select(); my $row; push @mailings, $row while $row = $sth->fetchrow_hashref(); my $mailstr; for (@mailings) { my $completed_condition = new GT::SQL::Condition; $completed_condition->add(mailing_id => '=' => $$_{mailing_id}); $completed_condition->add(mailing_done => IS => \'NOT NULL'); my $completed = $DB->table('MailingIndex')->count($completed_condition); my ($total,$done); unless ($completed) { $total = $DB->table('EmailRecipient')->count({ mailing_id_fk => $$_{mailing_id} }); $done = $DB->table('EmailRecipient')->count({ recipient_sent => 1, mailing_id_fk => $$_{mailing_id} }); } $mailstr .= < ID: $$_{mailing_id} | Subject: @{[GT::CGI::html_escape($$_{mailing_subject}) || '']} | @{[$completed ? "This mailing has been completed." : "$done/$total sent."]} | Details | @{[$completed ? "" : qq~@{[$done ? "Continue" : "Start"]} mailing |~]} @{[$completed ? "Delete" : "Cancel"]} mailing MAILING } $mailstr ||= "There are no mailings to display."; GForum::page( "email_mailings.html", { mailings => $mailstr, error => $error || "" } ); } # "View Mailings" -> "Details" sub GForum::MassMailer::ACTIONS::show_mailing_detail { my $mailing = $IN->param('mailing'); my %info = %{$DB->table('MailingIndex')->select({ mailing_id => $mailing })->fetchrow_hashref()}; my $count = $DB->table('EmailRecipient')->count({ mailing_id_fk => $mailing }); ($info{mailing_message} = GT::CGI::html_escape($info{mailing_message}) || '') =~ s/(\r?\n)/
$1/g; my $finished; $finished = make_date_string($info{mailing_done}) if defined $info{mailing_done}; GForum::page( "email_mailing_detail.html", { map({ ($_ => GT::CGI::html_escape($info{$_}) || '') } qw{ mailfrom name subject messageformat }), mailfrom => GT::CGI::html_escape($info{mailing_from}) || '', name => GT::CGI::html_escape($info{mailing_name}) || '', subject => GT::CGI::html_escape($info{mailing_subject}) || '', messageformat => GT::CGI::html_escape($info{mailing_format}) || '', message => $info{mailing_message}, id => $mailing, count => $count, finished => $finished } ); } # "View Mailings" -> "Cancel mailing", "Delete mailing" sub GForum::MassMailer::ACTIONS::cancel_mailing { my $mailing = $IN->param('mailing'); my %info = %{$DB->table('MailingIndex')->select({ mailing_id => $mailing })->fetchrow_hashref()}; my $count = $DB->table('EmailRecipient')->count({ mailing_id_fk => $mailing }); ($info{mailing_message} = GT::CGI::html_escape($info{mailing_message}) || '') =~ s/\r?\n/
\n/g; my $finished; $finished = make_date_string($info{mailing_done}) if defined $info{mailing_done}; GForum::page( "email_confirm_cancel_mailing.html", { Verbtion => defined $info{mailing_done} ? "Deletion" : "Cancellation", verbed => defined $info{mailing_done} ? "deleted" : "cancelled", mailfrom => GT::CGI::html_escape($info{mailing_from}) || '', name => GT::CGI::html_escape($info{mailing_name}) || '', subject => GT::CGI::html_escape($info{mailing_subject}) || '', messageformat => $info{mailing_format}, message => $info{mailing_message}, id => $mailing, count => $count, finished => $finished } ); } # "View Mailings" -> "Cancel mailing","Delete mailing" -> "Confirm Mailing Deletion","Confirm Mailing Cancellation" sub GForum::MassMailer::ACTIONS::confirmed_cancel_mailing { my $mailing = $IN->param('mailing'); $DB->table('MailingIndex')->delete({ mailing_id => $mailing }); $GForum::MassMailer::ACTIONS::{mailings}->("Selected mailing has been ".$IN->param('verbed')); } # "View Mailings" -> "Delete all completed mailings" sub GForum::MassMailer::ACTIONS::delete_finished_mailings { my $cond = new GT::SQL::Condition; $cond->add(mailing_done => 'IS NOT' => \'NULL'); GForum::page( "email_confirm_delete_finished_mailings.html", { count => $DB->table('MailingIndex')->count($cond) } ); } # "View Mailings" -> "Delete all completed mailings" -> "Confirm Delete All Finished Mailings" sub GForum::MassMailer::ACTIONS::confirmed_delete_finished_mailings { my $cond = new GT::SQL::Condition; $cond->add('mailing_done' => 'IS NOT' => \'NULL'); my @mailing = $DB->table('MailingIndex')->select('mailing_id',$cond)->fetchall_list; for (@mailing) { $DB->table('MailingIndex')->delete({ mailing_id => $_ }); } $GForum::MassMailer::ACTIONS::{mailings}->("All finished mailings have been deleted"); } # "View Mailings" -> "Cancel/Delete all mailings" sub GForum::MassMailer::ACTIONS::delete_all_mailings { GForum::page( "email_confirm_delete_all_mailings.html", { count => $DB->table('MailingIndex')->count() } ); } # "View Mailings" -> "Cancel/Delete all mailings" -> "Confirm Delete All Mailings" sub GForum::MassMailer::ACTIONS::confirmed_delete_all_mailings { $DB->table('MailingIndex')->delete_all; $GForum::MassMailer::ACTIONS::{mailings}->("All mailings have been cancelled and/or deleted"); } 1;