denny/ShinyCMS

View on GitHub
tasks/send-newsletters

Summary

Maintainability
Test Coverage
#!/usr/bin/env perl

# ===================================================================
# File:        tasks/send-newsletters
# Project:    ShinyCMS
# Purpose:    Check for queued newsletter mailshots and send them
# 
# Author:    Denny de la Haye <2019@denny.me>
# Copyright (c) 2009-2019 Denny de la Haye
# 
# ShinyCMS is free software; you can redistribute it and/or modify it
# under the terms of either the GPL 2.0 or the Artistic License 2.0
# ===================================================================

use strict;
use warnings;


# Local modules
use FindBin qw( $Bin );
use lib "$Bin/../lib";
use ShinyCMS;
use ShinyCMS::Schema;


# Exit if already running
use File::Pid;
my $pidfile = File::Pid->new({
    file => '/tmp/shinycms-send-newsletters.'. ShinyCMS->config->{ domain } .'.pid',
});
if ( my $num = $pidfile->running ) {
    die "Already running: $num\n";
}
$pidfile->write;


# CPAN modules
use Template;
use Email::MIME;
use Email::Sender::Simple qw( try_to_sendmail );


my $debug = 0;    # enable/disable debugging output


# Get a database connection
my $schema = ShinyCMS::Schema->connect(
    ShinyCMS->config->{ 'Model::DB' }->{ connect_info }
);

# Get the current date and time
my $now = DateTime->now;

# Find newsletters that are marked or scheduled for sending
my @newsletters = $schema->resultset( 'Newsletter' )->search([
    { status => 'Test'   },
    { status => 'Queued' },
]);

# Loop through the newsletters
foreach my $newsletter ( @newsletters ) {
    print 'Processing newsletter: ', $newsletter->title, "\n" if $debug;
    
    # Build up the newsletter body data
    my $data = {};
    $data->{ newsletter } = $newsletter;
    $data->{ site_name  } = ShinyCMS->config->{ site_name };
    $data->{ site_url   } = 'http://'. ShinyCMS->config->{ domain };
    
    # Get newsletter elements
    my @elements = $newsletter->newsletter_elements->all;
    $data->{ newsletter_elements } = \@elements;
    
    # Build up 'elements' structure for use by templates
    foreach my $element ( @elements ) {
        $data->{ elements }->{ $element->name } = $element->content;
    }
    
    # Create the template object
    my $tt = Template->new({
        INCLUDE_PATH => ShinyCMS->path_to( 'root/newsletters/newsletter-templates' ),
    });
    
    # Build up the email
    my @parts = (
        Email::MIME->create(
            attributes => {
                content_type => "text/plain",
                charset      => "UTF-8",
            },
        ),
        Email::MIME->create(
            attributes => {
                content_type => "text/html",
                charset      => "UTF-8",
            },
        ),
    );
    my $email_from = '"' . ShinyCMS->config->{ site_name } 
        . '" <' . ShinyCMS->config->{ site_email } . '>';
    my $email = Email::MIME->create(
        header => [
            From    => $email_from,
            Subject => $newsletter->title,
            'Content-Type' => 'multipart/alternative; charset="UTF-8"',
        ],
    );
    
    # Fetch the list of intended subscribers
    my @subscribers = $newsletter->list->subscribers->all;
    
    # Send email to each subscriber
    my $email_sent = 0;
    foreach my $subscriber ( @subscribers ) {
        print 'Sending email to ', $subscriber->name, "\n" if $debug;
        
        # Set the subscriber in the email header
        $email->header_set(
            To => $subscriber->name .' <'. $subscriber->email .'>'
        );
        
        # Add the subscriber name+email to the template data
        $data->{ subscriber } = $subscriber;
        
        # Build the newsletter
        my $html_body = '';
        $tt->process( $newsletter->template->filename, $data, \$html_body ) 
            || die $tt->error, "\n";
        
        # Make image src URLs absolute and URL-encoded
        $html_body =~ s/src="([^"]+)"/fix_URLs($1)/egis;
        
        # Remove DOS line-endings, because they confuse Outlook.com (?!)
        $html_body =~ s/\r\n/\n/;
        
        # Set the newsletter as the body of the email
        $parts[0]->body_set( $newsletter->plaintext );
        $parts[1]->body_set( $html_body );
        $email->parts_set( [ @parts ] );
        
        # Send the email
        my $status = try_to_sendmail( $email );
        if ( $status ) {
            $email_sent = 1;
        }
        else {
            # Sending failed
            print STDERR "Failed to send email to ", $subscriber->email, "\n";
            print STDERR $email->as_string, "\n" if $debug;
        }
    }
    
    if ( $email_sent ) {
        # Update the newsletter status appropriately
        if ( $newsletter->status eq 'Test' ) {
            $newsletter->update({ status => 'Not sent' });
        }
        else {
            $newsletter->update({ status => 'Sent' });
        }
    }
    else {
        # Output an error
        print STDERR "No emails sent.  Epic fail.\n";
    }
}

# Remove the PID file
$pidfile->remove;


sub fix_URLs {
    my $url = shift @_;
    
    # URL encoding for spaces
    $url =~ s/ /%20/g;
    
    # Absolute URLs
    my $site_url = 'http://'. ShinyCMS->config->{ domain };
    unless ( $url =~ m{^https?://} ) {
        $url = $site_url . $url;
    }
    
    return 'src="'. $url .'"';
}