(Swift) PHP Mailer class with plugin support

Small, short code snippets that other people may find useful. Do you have a good regex that you would like to share? Share it! Even better, the code can be commented on, and improved.

Moderator: General Moderators

User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: utf-8 support?

Post by Chris Corbyn »

akreider wrote:Does this have utf-8 support? I'm getting my software to work in unicode so that it will work in 99% of the world's languages.

I'm not sure if you'd want to build-in utf-8 support, or if there is something that programmers should be doing before they get the email text into your script?

I've read something about needing to convert the email text to base64... I guess I'd want to be able to have the email text, subject header, and to: header in unicode.
I did make it all utf-8 but some clients weren't rendering it correctly (perhaps I did it wrong) so I followed what thunderbird was doing instead. Odd thing is I could swear I switched to a ISO-8859-1 charset to get that working and somehow I've reverted back to UTF-8.

I'll just test a few things and post back.

EDIT | Latest version should be using ISO-8859-1 by default, but change it to UTF-8 using setCharset() and it should work as UTF-8 but like I say it wasn't rendering certain characters correctly in thunderbird using that charset so I made ISO-8859-1 default. Maybe somebody can help me out on this one?

Here's the key part:

Code: Select all

/**
	 * Encode a string (mail) in a given format
	 * Currently supports:
	 *  - BASE64
	 *  - Quoted-Printable
	 *  - Ascii 7-bit
	 *  - Ascii 8bit
	 *  - Binary (not encoded)
	 *
	 * @param  string  input
	 * @param  string  encoding
	 * @return  string  encoded output
	 */
	public function encode($string, $type)
	{
		$type = strtolower($type);
		
		switch ($type)
		{
			case 'base64':
			return base64_encode($string);
			break;
			//
			case 'quoted-printable':
			return $this->quotedPrintableEncode($string);
			//
			case '7bit':
			case '8bit':
			if (strtoupper($this->charset) != 'UTF-8') return utf8_decode($string);
			break;
			case 'binary':
			default:
			break;
		}
		
		return $string;
	}

// snip //

	/**
	 * Add a part to a multipart message
	 * @param  string  body
	 * @param  string  content-type, optional
	 * @param  string  content-transfer-encoding, optional
	 * @return  void
	 */
	public function addPart($string, $type='text/plain', $encoding='8bit')
	{
		$body_string = $this->encode($string, $encoding);
		if ($this->autoCompliance && $encoding != 'binary') $body_string = $this->chunkSplitLines($body_string);
		$ret = "Content-Type: $type; charset="{$this->charset}"\r\n".
				"Content-Transfer-Encoding: $encoding\r\n\r\n".
				$body_string;
		
		if (strtolower($type) == 'text/html') $this->parts[] = $this->makeSafe($ret);
		else $this->parts = array_merge((array) $this->makeSafe($ret), $this->parts);
	}
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

jayshields wrote:I've never sent an email with an attachment via PHP. It's becoming confusing for me. I decided to use Swift, but I can't find any attachment examples where it sends an email with a user uploaded file; they are always server side files.

Does that mean I have to upload the file, add it as attachment, send the email, then delete the uploaded file? I'm not 100% sure how attachments work anyways - would I have to wait until the user has recieved the email and grabbed the attachment before deleting the uploaded file?

Thanks.
Upload the file, using $_FILES['form_field_name']['tmp_name'] as the path:

Code: Select all

$swift->addAttachment($_FILES['foo']['tmp_name'], $_FILES['foo']['name'], $_FILES['foo']['type']);
PHP should remove temporary files itself if I remember correctly... you'd need to use move_uploaded_file() if you wanted to keep a copy on the server.
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Post by Benjamin »

I have a client who wants to know why swiftmailer is better than sendmail -t. Anyone?
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

Swift is pure php. More portable.
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Post by Benjamin »

Is it faster? More reliable? What else?
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

astions wrote:Is it faster? More reliable? What else?
Not faster than `sendmail -t'. Well.... it faster at sending batch emails because mail() keeps calling sendmail, while swift only calls it once. sendmail is pure linux. If you'd asked me if it's faster than mail() then yes, once you start sending more than one email it's faster.

EDIT | More reliable = Yes. You can port your code to other servers and not worry about their ini settings.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

OK, I'm just tidying up version 2.0 (yay!) which puts Swift well ahead now :)

New features:

* Swift can now happily deal with malformed, relay denied and reject addresses whilst still sending to everyone else... I believe PHPMailer chokes at the first malformed address when SMTP throws an error. This is the most significant change and a decent change to the code hence the jump to 2.0.
* UTF-8 support seems to be working from my tests - I'd quite like someone else to test it too though.
* Enhanced plugin API (force command skipping, getters for some other useful properties)
* Auto-detection for SMTP and Sendmail details (uses ini_get() and the linux command "which")

The last thing I want to do is to use the PEAR naming conventions for the files.

Question:

Rather than:

./Swift.php (Contains class Swift)
./Swift/Swift_SMTP_Connection.php (Contains class Swift_SMTP_Connection)

Should it be like this?

./Swift.php (Contains class Swift)
./Swift/Connection/SMTP.php (Contains class Swift_Connection_SMTP)

Because that's awful class naming, and I don't want my connections in different folders :(

Like, if I had:

Swift_Authenticator_LOGIN it seems less logic naming than Swift_LOGIN_Authenticator (since the authentication mechanism is called "LOGIN"). But using my current name I'd have to do:

./Swift/LOGIN/Authenticator.php (Swift_LOGIN_Authenticator)

PEAR naming seems to suck in this respect :(
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

Swift_Authenticator_LOGIN or Swift_LOGINAuthenticator. :-D (What this means is yes, you should be able to figure out a file path by taking a class and replacing the underscores with slashes).

Recently, I relaxed one of my old rules that class naming must reflect inheritance, and just put all the necessary require_once's in the files.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

Hmm thanks. I'm in two minds whether to just keep it the same way or not. A big issue with changing the layout and naming now is that I have loads of users who are going to want to update their Swift version but unlike all the other updates that have been done, this wouldn't "just work" if you replaced the library with the newer one. That would put people off switching versions.
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

Create a Compat file that creates aliases to the old class names.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

Ambush Commander wrote:Create a Compat file that creates aliases to the old class names.
:?: How does that work?

So I rename my files, for example:

./Swift/Swift_Sendmail_Connection.php (Swift_Sendmail_Connection)

Becomes:

./Swift/Connection/Sendmail.php (Swift_Connection_Sendmail)

Then if a user has this in their code from an old version:

Code: Select all

require_once('./Swift/Swift_Sendmail_Connection.php');
$connection = new Swift_Sendmail_Connection;
How can I have that automatically translate to:

Code: Select all

require_once('./Swift/Connection/Sendmail.php');
$connection = new Swift_Connection_Sendmail;
Thanks :) Still in two minds.... I'll mull it over while I finish writing these two new connection handlers (ermm... wrappers for multiple redundant connections & a connection cycler/load balancer).
User avatar
Ambush Commander
DevNet Master
Posts: 3698
Joined: Mon Oct 25, 2004 9:29 pm
Location: New Jersey, US

Post by Ambush Commander »

Swift/Swift_Sendmail_Connection.php

Code: Select all

// Compatibility stub
require_once(dirname(__FILE__) . 'Connection/Sendmail.php');
class Swift_Sendmail_Connection extends Swift_Connection_Sendmail {}
You could even add in an optional check the warns the user that they should change their include path/class name or disable the stubs if compatibility mode hasn't been explicitly turned on.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

Ambush Commander wrote:Swift/Swift_Sendmail_Connection.php

Code: Select all

// Compatibility stub
require_once(dirname(__FILE__) . 'Connection/Sendmail.php');
class Swift_Sendmail_Connection extends Swift_Connection_Sendmail {}
You could even add in an optional check the warns the user that they should change their include path/class name or disable the stubs if compatibility mode hasn't been explicitly turned on.
Ah cool thanks, so obvious now. That's gonna be a lot of extra files, although I guess it only needs to stay there for a few releases. I'll also need to switch my "private" properties to "protected", although that's perhaps a good thing ;)

I'm not sure I'll do it or not.... Ah screw it. I'll make a folder "compat". Place a news article on SF and the website and send to the mailing list just explaining the significant change and that you can copy the file from ./compat to ./Swift if you want it to "just work".

That's tidy I guess.... If I don't do it at a major version change it's less sensible doing it at a minor release and some people have suggested that my current naming isn't very friendly for service locators and autoloaders.

Thanks again, that's a big help! :)
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

Version 2.0 is now available. New features:
NOTE: If you're upgrading from Swift 1.3 or lower be sure to read the read me in the "compat" directory since the names and paths have changed, but the files provided here should keep things running smoothly.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

Somewhat experimental so I'm not putting this in the main package just yet but if you send mail from contact forms etc and are becoming a victim of spam this might help.

You need SpamAssassin installed on the server. Most hosts that provide email will have this (especially CPanel hosts!) and if it's your own server you can get it free.

This plugin passes the email through spamassassin and checks the score it got. If it's score is higher than the limit you set, the email is rejected as spam and swift stops sending.

Code: Select all

<?php

/**
 * Spam Checking plugin for Swift Mailer.
 *
 * @package	Swift
 * @version	>= 2.0.0
 * @author	Chris Corbyn
 * @date	30th July 2006
 * @license	http://www.gnu.org/licenses/lgpl.txt Lesser GNU Public License
 *
 * @copyright Copyright &copy; 2006 Chris Corbyn - All Rights Reserved.
 * @filesource
 * 
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to
 *
 *   The Free Software Foundation, Inc.,
 *   51 Franklin Street,
 *   Fifth Floor,
 *   Boston,
 *   MA  02110-1301  USA
 *
 *    "Chris Corbyn" <chris@w3style.co.uk>
 *
 */

/**
 * Scans the email for anything that looks like spam using SpamAssassin.
 * If the score obtained is higher than what is set here, sending stops and an error is logged.
 * @package Swift
 * @author Chris Corbyn
 */
class Swift_Plugin_SpamCheck implements Swift_IPlugin
{
	/**
	 * Plugin ID
	 * @var string name
	 */
	public $pluginName = 'SpamCheck';
	/**
	 * A reference to the main Swift object
	 */
	private $swiftInstance;
	/**
	 * The score at which we cast the message away as spam
	 * @var int score
	 */
	private $limit;
	/**
	 * The location of the spamassassin perl script
	 * @var string path
	 */
	private $pathToSpamassassin = '/usr/bin/spamassassin';
	
	/**
	 * Constructor
	 * @param int limit (score)
	 * @param string path to spamassassin, optional
	 */
	public function __construct($limit=1.5, $path=false)
	{
		$this->limit = (float) $limit;
		if ($path) $this->pathToSpamassassin = $path;
	}
	/**
	 * Load a reference to swift
	 */
	public function loadBaseObject(&$swift)
	{
		$this->swiftInstance =& $swift;
	}
	/**
	 * Get the score spamassassin gave the message
	 * @param string altered message
	 * @return float score
	 */
	private function getScore($result)
	{
		$spam_header = preg_match('/^X-Spam-Status: (?:Yes|No), score=(\S+)\s/m', $result, $matches);
		$score = $matches[1];
		return (float) $score;
	}
	/**
	 * onBeforeSend event handler
	 * Kills Swift and logs and error if the email is spam
	 */
	public function onBeforeSend()
	{
		$mail = substr($this->swiftInstance->currentMail[3], 0, strrpos($this->swiftInstance->currentMail[3], '.'));
		$spamassassin = $this->pathToSpamassassin;
		$result = @shell_exec("echo ".escapeshellarg($mail)." | $spamassassin -Lt");
		if (!empty($result))
		{
			$score = $this->getScore($result);
			
			if ($score >= $this->limit)
			{
				$this->swiftInstance->logError('WARNING SpamCheck Plugin!!!  Message logged as SPAM with a score of '.$score.' where '.$this->limit.' required');
				$this->swiftInstance->close();
				$this->swiftInstance->fail();
			}
		}
	}
}

?>
I tested it with the GTUBE spam checking message.... Swift did indeed disconnect and log an error regarding the spam. You'll probably need to set a very low score compared to using SpamAssassin normally since if the email hasn't left the server it can't be blacklisted so it will be based purely on content.
Post Reply