Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #34: Added support for attachments #35

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Once you have verified your sending domain, you are all good to go!
Other Commands
=======

`wp aws-ses send <to> <subject> <message> [--from-email=<email>]`
`wp aws-ses send <to> <subject> <message> [--from-email=<email>] [--attachments=/path/to/file1,/path/to/file2]`

Send a test email via the command line. Good for testing!

Expand Down
249 changes: 212 additions & 37 deletions inc/class-ses.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,8 @@ public function __construct( $key, $secret, $region = null ) {
* using the AWS SDK.
*
* @todo support cc, bcc
* @todo support attachments
* @since 0.0.1
* @access public
* @todo Add support for attachments
* @param string $to
* @param string $subject
* @param string $message
Expand Down Expand Up @@ -134,59 +132,236 @@ public function send_wp_mail( $to, $subject, $message, $headers = array(), $atta
}

try {
$args = array(
'Source' => $message_args['headers']['From'],
'Destination' => array(
'ToAddresses' => $message_args['to']
),
'Message' => array(
'Subject' => array(
'Data' => $message_args['subject'],
'Charset' => get_bloginfo( 'charset' ),
$args = array();
if ( ! empty( $attachments ) ) {
$args = array(
'RawMessage' => array(
'Data' => $this->get_raw_message( $to, $subject, $message, $headers, $attachments ),
),
'Body' => array(),
),
);

if ( isset( $message_args['text'] ) ) {
$args['Message']['Body']['Text'] = array(
'Data' => $message_args['text'],
'Charset' => get_bloginfo( 'charset' ),
);
}

if ( isset( $message_args['html'] ) ) {
$args['Message']['Body']['Html'] = array(
'Data' => $message_args['html'],
'Charset' => get_bloginfo( 'charset' ),
$args = apply_filters( 'aws_ses_wp_mail_ses_send_raw_message_args', $args, $to, $subject, $message, $headers, $attachments );

$result = $ses->sendRawEmail( $args );
} else {
$args = array(
'Source' => $message_args['headers']['From'],
'Destination' => array(
'ToAddresses' => $message_args['to']
),
'Message' => array(
'Subject' => array(
'Data' => $message_args['subject'],
'Charset' => get_bloginfo( 'charset' ),
),
'Body' => array(),
),
);
}

if ( ! empty( $message_args['headers']['Reply-To'] ) ) {
$replyto = explode( ',', $message_args['headers']['Reply-To'] );
$args['ReplyToAddresses'] = array_map( 'trim', $replyto );
}
if ( isset( $message_args['text'] ) ) {
$args['Message']['Body']['Text'] = array(
'Data' => $message_args['text'],
'Charset' => get_bloginfo( 'charset' ),
);
}

foreach ( [ 'Cc', 'Bcc'] as $type ) {
if ( empty( $message_args['headers'][ $type ] ) ) {
continue;
if ( isset( $message_args['html'] ) ) {
$args['Message']['Body']['Html'] = array(
'Data' => $message_args['html'],
'Charset' => get_bloginfo( 'charset' ),
);
}

$addrs = explode( ',', $message_args['headers'][ $type ] );
$args['Destination'][ $type . 'Addresses' ] = array_map( 'trim', $addrs );
}
if ( ! empty( $message_args['headers']['Reply-To'] ) ) {
$replyto = explode( ',', $message_args['headers']['Reply-To'] );
$args['ReplyToAddresses'] = array_map( 'trim', $replyto );
}

foreach ( [ 'Cc', 'Bcc' ] as $type ) {
if ( empty( $message_args['headers'][ $type ] ) ) {
continue;
}

$addrs = explode( ',', $message_args['headers'][ $type ] );
$args['Destination'][ $type . 'Addresses' ] = array_map( 'trim', $addrs );
}

$args = apply_filters( 'aws_ses_wp_mail_ses_send_message_args', $args, $message_args );


$args = apply_filters( 'aws_ses_wp_mail_ses_send_message_args', $args, $message_args );
$result = $ses->sendEmail( $args );
$result = $ses->sendEmail( $args );
}
} catch ( Exception $e ) {
do_action( 'aws_ses_wp_mail_ses_error_sending_message', $e, $args, $message_args );

return new WP_Error( get_class( $e ), $e->getMessage() );
}

do_action( 'aws_ses_wp_mail_ses_sent_message', $result, $args, $message_args );

return true;
}

/**
* Generate raw multipart email string.
*
* @param array|string $to
* @param string $subject
* @param string $message
* @param array $headers
* @param array $attachments
*
* @return string
*/
protected function get_raw_message( $to, $subject, $message, $headers = array(), $attachments = array() ) {
// Initial headers
$custom_from = false;
$raw_message_header = '';

$cc = $bcc = $reply_to = array();

// Filter initial content type, if custom header present then it will overwrite it.
$content_type = apply_filters( 'wp_mail_content_type', 'text/plain' );

if ( ! empty( $headers ) ) {
// Iterate through the raw headers
foreach ( $headers as $name => $content ) {
$name = trim( $name );
$content = trim( $content );
switch ( strtolower( $name ) ) {
case 'from':
// Gravity forms allow custom from header, so it will overwrite the from value.
$custom_from = $content;
break;
case 'content-type':
//if content-type header present and contains charset details, extract it for multipart.
if ( strpos( $content, ';' ) !== false ) {
list( $type, $charset_content ) = explode( ';', $content );
$content_type = trim( $type );
if ( false !== stripos( $charset_content, 'charset=' ) ) {
$charset = trim( str_replace( array( 'charset=', '"' ), '', $charset_content ) );
} elseif ( false !== stripos( $charset_content, 'boundary=' ) ) {
$boundary = trim( str_replace( array(
'BOUNDARY=',
'boundary=',
'"'
), '', $charset_content ) );
$charset = '';
}
} elseif ( '' !== trim( $content ) ) {
$content_type = trim( $content );
}
break;
case 'cc':
$cc = array_merge( (array) $cc, explode( ',', $content ) );
break;
case 'bcc':
$bcc = array_merge( (array) $bcc, explode( ',', $content ) );
break;
case 'reply-to':
$reply_to = array_merge( (array) $reply_to, explode( ',', $content ) );
break;
default:
$raw_message_header .= $name . ': ' . str_replace( array(
"\r\n",
"\r",
"\n"
), "", $content ) . "\n";
break;
}
}
}

// Get the site domain and get rid of www.
$sitename = strtolower( parse_url( site_url(), PHP_URL_HOST ) );
if ( 'www.' === substr( $sitename, 0, 4 ) ) {
$sitename = substr( $sitename, 4 );
}

$from_email = 'wordpress@' . $sitename;

// If custom from address is not present in header, generate it.
if ( ! $custom_from ) {
$custom_from = sprintf( '%s <%s>', apply_filters( 'wp_mail_from_name', get_bloginfo( 'name' ) ), apply_filters( 'wp_mail_from', $from_email ) );
}
$boundary = 'aws-ses-wp-mail-' . wp_rand();
$raw_message = $raw_message_header;
$raw_message .= 'To: ' . $this->trim_recipients( $to ) . "\n";
$raw_message .= 'From: ' . $custom_from . "\n";
$raw_message .= 'Reply-To: ' . $this->trim_recipients( $reply_to ) . "\n";

if ( ! empty( $cc ) ) {
$raw_message .= 'CC: ' . $this->trim_recipients( $cc ) . "\n";
}
if ( ! empty( $bcc ) ) {
$raw_message .= 'BCC: ' . $this->trim_recipients( $bcc ) . "\n";
}

if ( $subject != null && strlen( $subject ) > 0 ) {
$raw_message .= 'Subject: ' . $subject . "\n";
}

$raw_message .= 'MIME-Version: 1.0' . "\n";
$raw_message .= sprintf( 'Content-Type: Multipart/Mixed; boundary="%s"', esc_attr( $boundary ) ) . "\n";
$raw_message .= sprintf( "\n--%s\n", $boundary );
$raw_message .= sprintf( 'Content-Type: Multipart/Alternative; boundary="alt-%s"', $boundary ) . "\n";

$charset = empty( $charset ) ? '' : sprintf( '; charset="%s";', esc_attr( $charset ) );
if ( $content_type && strpos( $content_type, 'text/plain' ) === false && strlen( $message ) > 0 ) {
$raw_message .= sprintf( "\n--alt-%s\n", $boundary );
$raw_message .= sprintf( 'Content-Type: text/html%s', $charset ) . "\n\n";
$raw_message .= $message . "\n";
} else if ( strlen( $message ) > 0 ) {
$raw_message .= sprintf( "\n--alt-%s\n", $boundary );
$raw_message .= sprintf( 'Content-Type: text/plain%s', $charset ) . "\n\n";
$raw_message .= $message . "\n";
}
$raw_message .= sprintf( "\n--alt-%s--\n", $boundary );

foreach ( $attachments as $attachment ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@faishal - wp_mail $attachments param also accepts a single string file location as a valid param. Currently, passing a single string results in attachments not being sent here.

We could follow cores approach and cast as follows (before entering the loop here)

    if ( ! is_array( $attachments ) ) {
        $attachments = explode( "\n", str_replace( "\r\n", "\n", $attachments ) );
    }

if ( ! file_exists( $attachment ) ) {
continue;
}

$filename = basename( $attachment );

$data = file_get_contents( $attachment );

$file_type = wp_check_filetype( $filename );

// If mime type is not present, set it as octet-stream
if ( ! $file_type['type'] ) {
$file_type['type'] = 'application/octet-stream';
}

$raw_message .= sprintf( "\n--%s\n", $boundary );
$raw_message .= sprintf( 'Content-Type: %1$s; name="%2$s"', $file_type['type'], esc_attr( $filename ) ) . "\n";
$raw_message .= 'Content-Disposition: attachment' . "\n";
$raw_message .= 'Content-Transfer-Encoding: base64' . "\n";
$raw_message .= "\n" . chunk_split( base64_encode( $data ), 76, "\n" ) . "\n";
}

$raw_message .= sprintf( "\n--%s--\n", $boundary );

return $raw_message;
}


/**
* Trim recipients addresses.
*
* @param string|array $recipient Single recipient or array of recipients
*
* @return string Trimmed recipients joined with comma
*/
public function trim_recipients( $recipient ) {
if ( is_array( $recipient ) ) {
return join( ', ', array_map( array( $this, 'trim_recipients' ), $recipient ) );
}

return trim( $recipient );
}

/**
* Get the client for AWS SES.
*
Expand Down
12 changes: 9 additions & 3 deletions inc/class-wp-cli-command.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class WP_CLI_Command extends \WP_CLI_Command {
*
* [--bcc=<bcc>]
* : Email addresses to BCC (comma-separated).
*
* [--attachments=<attachments>]
* : Attachments paths(comma-separated).
*/
public function send( $args, $args_assoc ) {

Expand All @@ -41,8 +44,8 @@ public function send( $args, $args_assoc ) {
return $args_assoc['from-email'];
});
}

$headers = [];
$headers = [];
$attachments = [];
if ( ! empty( $args_assoc['reply-to'] ) ) {
$headers['Reply-To'] = $args_assoc['reply-to'];
}
Expand All @@ -52,8 +55,11 @@ public function send( $args, $args_assoc ) {
if ( ! empty( $args_assoc['bcc'] ) ) {
$headers['BCC'] = $args_assoc['bcc'];
}
if ( ! empty( $args_assoc['attachments'] ) ) {
$attachments = explode( ',', $args_assoc['attachments'] );
}

$result = SES::get_instance()->send_wp_mail( $args[0], $args[1], $args[2], $headers );
$result = SES::get_instance()->send_wp_mail( $args[0], $args[1], $args[2], $headers, $attachments );

if ( is_wp_error( $result ) ) {
WP_CLI::error( $result->get_error_code() . ': ' . $result->get_error_message() );
Expand Down