Forgot Password?

  • Home
  • Resources
    • Blog
    • News
    • Professional services
  • Projects
    • Webber
    • WB Ticket System
    • WB Blog
  • Contact
  • Support
    • Wiki
    • Forum
    • Ticket system

DrSoft Blog

sharing thoughts, ideas...

  • Tags

    • css
    • galleries
    • drsoft
    • webber
    • open source
    • applications
    • developers
    • Webber
    • protection
    • guide
    • files
    • mod_rewrite
    • downloads
    • hotlinking
    • htaccess
    • leechers
    • menu
    • programming
    • plugins
    • ajax
    • form validation
    • jquery
    • php
    • ide
    • editors
    • progress bar css php
    • modules
    • blog
    • speedy
    • codeigniter
    • buttons
    • html
    • email
    • phpmailer
    • sendmail
    • smtp
    • validation. user friendly
    • file upload
    • multiple
    • login
    • secure

PHP Login - a simple and secure example Posted on 06-10-09, 09:59 PM by blog 0

The login page is among the most important parts of a web application as it sets the difference between guests and authenticated members. All your private content should be hidden from guests and only accessible if the user has performed a successfull authentication so this part needs a lot of attention and logic otherwise your plans might not work as intended. The login feature also needs to be very secure by performing various validations and checks before allowing anyone to enter the system but also transparent as we don't want to annoy our members with too much things.

In this tutorial we will learn how to build a highly secure login system which authenticates members, capable of handling persistent cookies (remember me) in a secure mode, prevents abuses from spam bots or brute force applications and many other things crucial too a strong and secure php login script.

We will start with the initial database schema which consists in one MySql table and explain every field in part for a better understanding of how things work.

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `password` char(32) COLLATE utf8_unicode_ci NOT NULL,
  `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `fingerprint` char(32) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`),
  UNIQUE KEY `email` (`email`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;


As you can see from our users table schema everything is pretty understandable except for the `fingerprint` field which is very important in our little application because it holds the user's 'fingerprint from the last login'. Every time the user loggs in and requests to be remembered we will store an md5 key which consists from the user's IP address and browser. Whenever someone with the same cookie stored in the browser tries to access our system and those parameters do not match we deny the user access so that's why the fingerprint. The more user information (try to find static values and values that are hard to emulate) you place in that key the better because it makes it harder for session hijackers to emulate a user's environment. Our bet will be on the user's ip because it's the hardest one to replicate among all the information that we can retrieve from a visitor.

Also, please notice that both the username and email are unique to prevent duplicates, pretty obvious.

You will notice the username field is stored in a varchar of 55 while our pasword is a char with only 32 characters. Why 32 and why char and not varchar. Well, first of all, the char field takes less space so it's a little optimization here, secondly, our passwords will be encrypted in md5 that outputs exactly 32 characters and locked with a salting key which adds extra protection to the md5 generated key. The most important thing is to store the passwords in a very safe way and hidden from everybody and this includes you or anyone else. Assuming that your salt key would be something like 'AG6-T6U-6*U' here's how the password will go into the database:

$salt_key = 'AG6-T6U-6*U';
$user_password = $_POST [ 'password' ];
$password = md5 ( $salt_key . md5 ( $user_password ) );


If you look better you will see that we use a double encryption and also a salt key so this should make the passwords pretty safe for storing in any place. Of course, the salting key should be defined in a file which, recommended, sits outside the document root of your webserver making it harder to access without ftp or ssh credentials. For the sake of this tutorial, we keep the salt key and cookie name in a class constant.

Ok, we know about the security measures that we will take so it's time to talk about our application's logic and start building it. Since it's a pretty basic thing we will place everything inside a PHP class to allow a better interaction between methods and properties and also encapsulate certain actions by making them private or protected. I will name the class 'Site_sentry':

class Site_sentry 


Right now it's empty because we didn't discussed about the usage of this class yet so let's try to make a list with everything needed for our login system to work properly. We will need to login members, set sessions, check login cookies, create the fingerprint and logout the members so here's the list:

- public function is_logged_in - checks to see if a member is logged in or not
- public function login - loggs a member in
- public function logout - loggs a member out
- private function set_login_sessions - sets the user sessions
- private function set_cookie - sets the user cookie
- private function check_cookie - checks if a member has a login cookie
- private function get_fingerprint - returns the user's fingerprint


As you can see I created only 3 class methods to be public, and the rest of them private as we might extend the class and change the method behavior from another class extending Site_sentry. The public methods will be the only ones to be used outside the class while the others are restricted to be used only within the class or subclasses which extend Site_sentry. Here's our code so far:

class Site_sentry {

	public function login ( $username, $password, $rememberme = FALSE ) {
		
	}

	public function get_last_error () {
		
	}

	public function logout () {
		
	}

	private function set_last_error () {

	}

	private function set_login_sessions ( $user_id ) {
		
	}

	private function unset_login_sessions () {
		
	}

	private function set_cookie ( $cookie_name, $value, $expires_in, $path = '/', $domain = 'yourdomain.com' ) {

	}

	private function check_cookie () {
		
	}

	private function get_fingerprint () {
		
	}

	private function set_fingerprint ( $user_id ) {

	}

	public function is_logged_in () {
		
	}

}


We have the skeleton of our application so let's build some logic now by creating the login functionality. We only need 2 parameters (3rd is optional) and we can grab them from $_POST or any other method, whatever suits you best. In 99% percent of the cases, we will have $_POST values comming from a web form - the login form:

public function login ( $username, $password, $rememberme = FALSE ) {
	$result = mysql_query ( 'SELECT id, password from users where username = ' . mysql_real_escape_string ( $username ) );
	if ( ! $result ) {
		$this->set_last_error ( "User not found" );
		return FALSE;
	}

	$row = mysql_fetch_row ( $result );

	if ( md5 ( SALT_KEY . md5 ( $password ) ) != $row [ 1 ] ) {
		$this->set_last_error ( "Invalid password" );
		return FALSE;
	}

	if ( $rememberme ) {
		$this->set_fingerprint ( $row [ 0 ] );
		$this->set_cookie ( self::COOKIE_NAME, $row [ 0 ] . $this->get_fingerprint (), time () + 3600 );
	}

	$this->set_login_sessions ( $row [ 0 ] );
}


At the very first level of our login method we extract the id and the password of the member that has the provided username. Many use a different aproach here by extracting the id where username and password are perfect match but we do it in another way in order to be able to tell what went wrong exactly. This way we can decide if the username exists or not because we're only checking the database against the user. If a record is not found we can simply echo that the user does not exists instead of a general type of error "Either the username does not exist or the provided password is incorrect". Moving further down the code it's time to perform the password check against the found user record so it's time to apply the encryption to the provided password:

md5 ( self::SALT_KEY . md5 ( $password ) )


If we have a match than it's a successful login so let's hurry up and store the cookie (if a remember me is present), set the sessions and let the member access the protected pages.

I'm not going to discuss how a cookie or sessions is set so I will just skip to the is_logged_in function which also has an important role in our php login system since it has to perform a check if a sessions is set and if not it will also have to look for a cookie and try to log the user in in case it finds one.

public function is_logged_in () {
	if ( isset ( $_SESSION [ self::COOKIE_NAME ] ) ) {
		return TRUE;
	}	

	if ( isset ( $_COOKIE [ self::COOKIE_NAME ] ) ) {
		$user_id = substr ( $_COOKIE [ self::COOKIE_NAME ], 0, 1 );
		$fingerprint = substr ( $_COOKIE [ self::COOKIE_NAME ], 1, strlen ( $_COOKIE [ self::COOKIE_NAME ] ) - 2 );

		$result = mysql_query ( 'SELECT id from users where id = ' . mysql_real_escape_string ( $user_id ) . ' AND fingerprint = ' . mysql_real_escape_string ( $fingerprint ) );
		if ( ! $result ) {
			//	set the cookie in the past to avoid this check again
			$this->set_cookie ( self::COOKIE_NAME, '', time () - 3600 );
			return FALSE;
		}
		else {
			$this->set_login_sessions ( $user_id );
		}
	}
}


The function first checks if there is a valid session and returns TRUE in case there is one. If no session is found it moves to checking if a cookie is present. If the cookie is found, we extract the id of the member from the cookie and the fingerprint since the cookie value is a concatenation of user id + fingerprint. Once extracted both values are matched against the database and, if found, we're restarting the sessions to avoid this check once again.

We have the login, we can check if the user is logged in or not and it's time to move to the logout function wich is very simple. Since it's a requested action we need to unset the login cookie and kill the user session.

public function logout () {
	//	set the cookie in the past to expire
	$this->set_cookie ( self::COOKIE_NAME, '', time () - 3600 );
	//	unset the sessions we previously set using 'set_login_sessions'
	$this->unset_login_sessions ();
	//	redirect the user to the login page
	header ( "Location: login.php" );
}


That's just about everything. In just a few simple steps we managed to create a login system which is very easy to use and secure. For a full and functional example don't forget to download the attached zip archive.

Here's our final code:

require_once "database.php";

class Site_sentry {

	const COOKIE_NAME = "LoggedIn";
	const SALT_KEY = 'AG6-T6U-6*U';
	
	protected $last_error;

	/**
	 *	Performs the user login with various checks
	 *	@param string $username The username
	 *	@param string $password The user password
	 *	@param boolean $rememberme The user wants to be remembered
	 *	return boolean User was logged in or not
	 */
	public function login ( $username, $password, $rememberme = FALSE ) {
		global $link;
		$result = mysql_query ( "SELECT id, password FROM users WHERE username = '" . mysql_real_escape_string ( $username ) . "'", $link ) or die ( 'MySql error: ' . mysql_error () );
		if ( ! $result ) {
			$this->set_last_error ( "User not found" );
			return FALSE;
		}

		$row = mysql_fetch_row ( $result );

		if ( $this->encode_password ( $password ) != $row [ 1 ] ) {
			$this->set_last_error ( "Invalid password" );
			return FALSE;
		}

		if ( $rememberme ) {
			$this->set_fingerprint ( $row [ 0 ] );
			$this->set_cookie ( self::COOKIE_NAME, $row [ 0 ] . '-' . $this->get_fingerprint (), time () + 3600 );
		}

		return $this->set_login_sessions ( $row [ 0 ] );
	}

	/**
	 *	If an error occurred this should return the last error
	 *	return string The last error
	 */
	public function get_last_error () {
		return $this->last_error;
	}

	/**
	 *	Logs the member out, also kills sessions and cookies
	 *	return boolean
	 */
	public function logout () {
		//	set the cookie in the past to expire
		$this->set_cookie ( self::COOKIE_NAME, '', time () - 3600 );
		//	unset the sessions we previously set using 'set_login_sessions'
		$this->unset_login_sessions ();
		return TRUE;
	}

	/**
	 *	Performs a check if the user is logged in or not
	 *	return boolean
	 */
	public function is_logged_in () {
		global $link;
		if ( isset ( $_SESSION [ self::COOKIE_NAME ] ) ) {
			return TRUE;
		}	

		if ( isset ( $_COOKIE [ self::COOKIE_NAME ] ) ) {
			$finger_data = explode ( '-', $_COOKIE [ self::COOKIE_NAME ] );
			$user_id = $finger_data [ 0 ];//	user id
			$fingerprint = $finger_data [ 1 ];//	fingerprint

			$result = mysql_query ( "SELECT id FROM users WHERE id = '" . mysql_real_escape_string ( $user_id ) . "' AND fingerprint = '" . mysql_real_escape_string ( $fingerprint ) . "'", $link ) or die ( 'MySql error: ' . mysql_error () );
			if ( ! $result || $fingerprint != $this->get_fingerprint () ) {
				//	user fingerprint does not match database data or cookie data
				//	set the cookie in the past to avoid this check again
				$this->set_cookie ( self::COOKIE_NAME, '', time () - 3600 );
				return FALSE;
			}
			else {
				$this->set_login_sessions ( $user_id );
				return TRUE;
			}
		}
		
		return FALSE;
	}

	/**
	 *	Encodes the user password with double md5 encryption and a salt key
	 *	@param string $password The user password
	 *	return string The encoded password
	 */
	private function encode_password ( $password ) {
		return md5 ( self::SALT_KEY . md5 ( $password ) );
	}

	/**
	 *	Stores the last error to be accessible via get_last_error ()
	 *	return void
	 */
	private function set_last_error ( $error ) {
		$this->last_error = $error;
	}

	/**
	 *	Sets the login user sessions
	 *	return boolean
	 */
	private function set_login_sessions ( $user_id ) {
		$_SESSION [ 'id' ] = $user_id;
		$_SESSION [ self::COOKIE_NAME ] = TRUE;
		return TRUE;
	}

	/**
	 *	Unsets the login user sessions
	 *	return boolean
	 */
	private function unset_login_sessions () {
		unset ( $_SESSION [ 'id' ], $_SESSION [ self::COOKIE_NAME ] );
		return TRUE;
	}

	/**
	 *	Sets the cookie if remember me was checked
	 *	return boolean
	 */
	private function set_cookie ( $self::COOKIE_NAME, $value, $expires_in, $path = '/', $domain = 'yourdomain.com' ) {
		setcookie ( $self::COOKIE_NAME, $value, $expires_in, $path, $domain );
	}

	/**
	 *	Performs a check if the remember me cookie is set
	 *	return boolean
	 */
	private function check_cookie () {
		return ( isset ( $_COOKIE [ self::COOKIE_NAME ] ) ) ? TRUE : FALSE;
	}

	/**
	 *	Returns the user fingerprint. User agent + IP..can be extended
	 *	return string fingerprint
	 */
	private function get_fingerprint () {
		//	this is basic implementation, feel free to change
		return md5 ( $_SERVER [ 'HTTP_USER_AGENT' ] . $_SERVER [ 'REMOTE_ADDR' ] );
	}

	/**
	 *	Sets the user fingerprint in the database
	 *	return boolean
	 */
	private function set_fingerprint ( $user_id ) {
		global $link;
		return mysql_query ( "UPDATE users SET fingerprint = '" . mysql_real_escape_string ( $this->get_fingerprint () ) . "' WHERE id = '" . mysql_real_escape_string ( $user_id ) . "'", $link ) or die ( 'MySql error: ' . mysql_error () );
	}

}


Usage example:

require_once "sentry.php";

$messages = array ();

$sentry = new Site_sentry ();


//	LOG IN
if ( ! $sentry->login ( 'test', 'test', TRUE ) ) {
	die ( $sentry->get_last_error () );
}
else {
	//	CHECK IF LOGGED IN
	if ( $sentry->is_logged_in () ) {
		$messages [] = "I am logged in as 'test'";
	}
}


//	LOG OUT
if ( ! $sentry->logout () ) {
	die ( $sentry->get_last_error () );
}
else {
	//	CHECK IF LOGGED OUT
	if ( ! $sentry->is_logged_in () ) {
		$messages [] = "Logged out successfully";
	}
}

echo '<pre>';
foreach ( $messages as $message ) {
	echo $message . "\n";
}
echo '</pre>';


Download link for a fully functional example:

login.zip Read more ...

PHP multiple file uploads Posted on 29-09-09, 01:19 AM by blog 0

Many times we need to perform multiple file uploads using the same web form but how do we do that? Better, how do we do it without even knowing how many files our user wants to upload. This is quite easy in PHP and it only needs a little more attention. Let's consider our form with a single input for a file:

<input type="file" name"picture" />


When submitted, if we print the $_FILES, variable we get something like this:

array (
	'name' => '',
	'tmp_name' => '',
	'error' => '',
	'size' => ''
)


This forces us to look for the name of the file input and try to access it from the $_FILES variable and so on for each file input we need. But this keeps us static and we can't really let our user upload as much as he wants so we need to modify our file input a little bit:

<input type="file" name"picture[]" />


The above type (notice the [] brackets placed at the end of the file name: picture[]) allows us to add as many file inputs with the same name. From now on, it's just a matter of adding a little javascript and a link under our file which, when clicked, will create new file inputs in order for our user to have control on the quantity.

function add_file_input(){
	$('#container-div').append('<input type="file" name="picture[]" />');
}


The above jquery snippet appends file inputs to a container with id set to 'container-div' so all we need to do right now is to wrap our file input in a div element with the required id.

<script type="text/javascript">
	function add_file_input(){
		$('#container-div').append('<input type="file" name="picture[]" />');
	}
</script>
<div id="container-div">
	<input type="file" name"picture[]" />
	<a href="javascript:void(0);" onclick="add_file_input();">Add field</a>
</div>


That's just about it on the client side. We now need to figure out a way to access all the files submitted by the user in a smart foreach loop to make sure we address them all.

foreach ( $_FILES [ 'picture' ] [ "error" ] as $key => $error ) {
	if ( $error == UPLOAD_ERR_OK ) {
		$tmp_name = $_FILES [ "picture" ] [ "tmp_name" ] [ $key ];
        	$name = $_FILES [ "pictures" ] [ "name" ] [ $key ];
		@move_uploaded_file ( $tmp_name, "$uploads_dir/$name" );
	}
}


That's it! In just 7 lines we managed to write the code which is capable of uploading an unlimited number of files (the only limitation is on the server, see max upload size, max execution time etc.) without too much hassle. Of course, you need to define the $uploads_dir/ variable and place the path to your uploads directory there. You also need to make sure it's writable by the server. Here's our final script:

<?php
	$uploaded_files = 0;
	$skipped_files = 0;
	$uploads_dir = realpath ( dirname ( __FILE__ ) ) . DIRECTORY_SEPARATOR . "uploads";

	if ( ! empty ( $_FILES ) ) {
		foreach ( $_FILES [ 'picture' ] [ "error" ] as $key => $error ) {
			if ( $error == UPLOAD_ERR_OK ) {
				$tmp_name = $_FILES [ "picture" ] [ "tmp_name" ] [ $key ];
		        	$name = $_FILES [ "picture" ] [ "name" ] [ $key ];
				if ( move_uploaded_file ( $tmp_name, "$uploads_dir/$name" ) ) {
					$uploaded = TRUE;
					$uploaded_files++;
				}
				else {
					$skipped_files++;
				}
			}
			else {
				$skipped_files++;
			}
		}
	}

	if ( $uploaded_files ) {
		echo "Upload finished! Uploaded files $uploaded_files, skipped $skipped_files";
	}
	else {
?>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
	function add_file_input(){
		$('#container-div').append('<input type="file" name="picture[]" /><br />');
	}
</script>
<form action="" enctype="multipart/form-data" method="post">
	<div id="container-div">
		<input type="file" name="picture[]" /><br />
	</div>
	<input type="submit" value="Upload" />
	<a href="javascript:void(0);" onclick="add_file_input();">Add field</a>
</form>
<?php } ?>
Don't forget to download our example:
multiple_uploads.zip Read more ...

PHP Validation done right Posted on 28-09-09, 09:47 PM by blog 0

Building forms is a pain for every programmer. The way you build them affects your user's experience and whatever has an impact on that is quite important. People hate filling forms as much as we hate building them. The process must be very straightforward and as friendly as possible otherwise the impact might affect your sales or whatever it is that you're looking to collect when building your form. In this tutorial we will try to track down the bottlenecks of a PHP form and find solutions to avoid them as much as possible in order to cut down the time required for our users to complete our forms and also follow our requirements or validators, however you want to call them.

A good form validation is done both on the client side and on the server side. Client side means that the validation is done in the browser using javascript. The advantage is that the form is not submitted, the page is not refreshed and the response is instant. The disadvantage is that you cannot perform complex validations or your client can simply turn off the javascript and all the magic is gone so it requires a backup plan on the server.

Since the tutorial is about PHP validation we're going to focus on the server side validation and learn how to build and validate user friendly web forms in PHP.

First, we need to make a list with things that we hate most when filling out forms:
- general errors ("Your information is not valid, please try again")
- the form does not remember my input after submitting it and I have to re-fill
- I don't know what's required or not
- all errors in a sigle place, usually at the top, causing me to ping-pong my eyes especially if I have a long form to fill
- errors like the above general one but also telling me to go back and try again

Now that we now what stresses people most, we need to address each and every one of them to make sure we're not doing these mistakes in our applications. Many programmers simply validate each field at each form in part the standard way but we're going to be smarter and make use of re-usable code. We're going to build a PHP class which can be instanciated in any place. Our scenario is actually built using 2 classes, one for the validation wich does the logic and the other holds the validators which consists of static methods just to perform the field validation and return boolean values like TRUE for passed, FALSE for failed.

The validation class is also supposed to store errors for each field in part in order to display them next to the field and nowhere else. This way is easier for our visitors to address them since the error message sites right next to the input. It also has to be smart enough and remember the values already posted by our visitor to avoid forcing him to re-fill all the inputs until everything is as requested by our validation scenario.

So, the form is submitted, the information is sent via POST and the server receives it. At that point, we instanciate our validation class and add all the validations to each field in part to make sure that what we receive and pass to further actions is the kind of data we expect. That's the whole point of a form validation anyways. Forcing people to submit what we expect making sure we don't encounter any errors.

We added the validation rules and next, we need to perform the validation itself, if any of the fields fail the validation, we stop the execution and present the form once again along with the errors marked in a nice red color. Once everything is received as we want and processed (database, email etc), we redirect the visitor to a success page and hide the form as we don't need it any more. This is what our target should be, a fast, smart and straightforward process for our visitors.

require_once "validation.php";

//	instanciate the validation class
$validation = new Validation;

//	subject validation rules
$validation->addField ( 'subject', 'required', 'This field is required' );
$validation->addField ( 'subject', 'min_length[5]', 'Your subject must contain at least 5 characters' );
$validation->addField ( 'subject', 'max_length[255]', 'Your subject must not contain more than 255 characters' );

//	name validation rules
$validation->addField ( 'name', 'required', 'This field is required' );
$validation->addField ( 'name', 'min_length[5]', 'Your name must contain at least 5 characters' );
$validation->addField ( 'name', 'max_length[255]', 'Your name must not contain more than 255 characters' );

//	email validation rules
$validation->addField ( 'email', 'required', 'This field is required' );
$validation->addField ( 'email', 'validEmail', 'Please add a valid email' );

//	message validation rules
$validation->addField ( 'message', 'required', 'This field is required' );
$validation->addField ( 'message', 'min_length[20]', 'Your message must contain at least 20 characters' );
$validation->addField ( 'message', 'max_length[2000]', 'Your message must not contain more than 2000 characters' );

//	execute the validation
if ( $validation->execute () ) {
	//	success, send the email, save the data in the database etc.
	header ( "Location: success.php" );
}

//	if we're here, the validation failed


As you can see, we can add unlimited validations to one field in part, each of them with a custom error message. This allows us to communicate better with the end user by building a smart, user friendly PHP form. Thank you for reading this, don't forget to download the example located at the end of this tutorial.

Validator.zip Read more ...

Advanced emailing in PHP Posted on 23-09-09, 10:35 PM by blog 0

Advanced emailing in PHP

Almost every application needs a strong emailing feature behind it to handle everything in a simple and yet powerful manner. Emailing sits at the grounds of your application by allowing you to create connections with your members and should be something very well done from the very start.

Many rely on the mail function which is added in PHP as a basic way of sending out emails from your website. The problem with this method is that it's not reliable, at all. The native mail function from PHP uses the server's IP address to send out emails so it's a huge problem from the start because many may use shared hostings and, implicitly, share the same ip addres which can be blacklisted. Also, it's not suitable for sending large amounts of emails because the mail function also opens and closes a SMTP socket for each mail delivery in part which is not efficient at all.

In this tutorial we will learn how to use an advanced emailing system which can:

- send a lot of emails to thounsands of recipients
- handle attachments
- is easy to use and re-use across your application
- uses SMTP authentication to ensure a higher delivery rate

First, let's download the PHPMailer package from (http://phpmailer.worxware.com/) which will serve as our main set of libraries for this project, otherwise this will be a very long tutorial. We will only create a single PHP class which uses PHPMailer to send out our emails:


class Email {

	public $phpMailerPath;

	

	//	TRUE/FALSE to use SMTP authentication or not

	private $_bUseSMTP = FALSE;

	//	Keeping it to TRUE, will throw exceptions if something bad

	//	happens. On FALSE, it will try to ignore errors and continue

	//	it's task

	public $bThrowExceptions = TRUE;

	//	Alternative body message for text only recipients

	public $sAltMessage = "To view the message, please use an HTML compatible email viewer!";



	//	Holds the recipients of the email

	private $_aRecipients = array ();

	//	Holds the attachments of the email, if any

	private $_aAttachments = array ();

	//	Sender's name

	private $_sFromName;

	//	Sender's email address

	private $_sFromEmail;

	//	Reply-to name

	private $_sReplyToName;

	//	Reply-to email address

	private $_sReplyToEmail;

	//	Default Smtp host

	private $_sSmtpHost;

	//	Default Smtp username

	private $_sSmtpUser;

	//	Default Smtp password

	private $_sSmtpPassword;

	//	Default Smtp port

	private $_sSmtpPort;
	//	Smtp authentication type: ssl, tls
	private $_sSmtpSecure;



	//	Sent messages counter

	private $_iSent = 0;

	//	Failed messages counter

	private $_iFailed = 0;

	//	Holds the errors

	private $_aErrors = array ();

	public function __construct () {}



	/**

	 * Used to send the email. Should be called last

	 * @param string $subject The email subject

	 * @param string $message The email body

	 * @return boolean TRUE/FALSE

	 */

	public function send ( $subject, $message ) {}



	/**

	 * Adds a recipient/receiver

	 * @param string $name The name of our recipient: e.g John Doe

	 * If the recipient name is not provided, the email is used instead

	 * @param string $email The email recipient

	 * @param array $search Should we look for things to replace? Maybe some tokens, e.g. {name}, {username}

	 * @param array $replace Replace values for the above searches

	 * @return void

	 */

	public function addRecipient ( $email, $name = FALSE, $search = array (), $replace = array () ) {}



	/**

	 * Adds an attachment to our email

	 * @param string $path Full path to our attachment file

	 * @param string $type Type of attachment (attachment/inline)

	 * @return void

	 */

	public function addAttachment ( $path, $type = 'attachment' ) {}



	/**

	 * Sets the 'reply to' headers. The receiver of replies should be defined here

	 * @param string $email The reply-to email address

	 * @param string $name The reply-to name: e.g John Doe

	 * If the reply-to name is not provided, the reply-to email is used instead

	 * @return void

	 */

	public function setReplyTo ( $email, $name = '' ) {}



	/**

	 * Sets the 'from' headers. The sender should be set here

	 * @param string $email The sender's email address

	 * @param string $name The sender's name: e.g John Doe

	 * @return void

	 */

	public function setFrom ( $email, $name = '' ) {}

	/**

	 * Returns the number of successfully sent messages

	 * @return int

	 */
	public function getSentCount () {}

	/**

	 * Returns the number of failed messages

	 * @return int

	 */
	public function getFailedCount () {}

	/**

	 * Sets the SMTP settings. If this is not set, the mailer will use sendmail or @mail

	 * @param string $host SMTP host

	 * @param string $user SMTP username
	 * @param string $pass SMTP password
	 * @param integer $port SMTP port, default 25

	 * @return void

	 */
	public function setSmtp ( $host, $user, $pass, $port = 25, $authType = '' ) {}

	/**

	 * Search replace for tokens. Either way, should output the message body

	 * @param string $message Message body

	 * @param mixed $search Array of tokens. Could be added as string as well
	 * @param mixed $replace Array of replacements. Should be string if search is string

	 * @return void

	 */
	private function processTokens ( $message, $search, $replace ) {}



	/**

	 * Private method for validating email addresses

	 * @param string $email The email address to be validated

	 * @return boolean TRUE/FALSE based on the result

	 */

	private function isValidEmail ( $email ) {}



	/**

	 * If an email fails to one of the recipients, call this function to log the error

	 * We might use this to clean our mailing lists

	 * @param string $email The email address associated with this error

	 * @param string $error The error message

	 * @return void

	 */

	private function logError ( $email, $error ) {}



}



Now that we have our skeleton class we need to build the logic of it and populate all those methods we defined above. We will use the 'send' method to send out the actual email so that's where we create the PHPMailer instance. This function should be called after everything else is being set (recipients, attachments etc). We will also log the errors if sending to recipients fails.


public function send ( $subject, $message ) {

	if ( ! count ( $this->_aRecipients ) ) {

		if ( $this->bThrowExceptions ) {

			throw new Exception ( "No recipients found. Email failed" );

		}

		return FALSE;

	}

	if ( ! file_exists ( $this->phpMailerPath . 'class.phpmailer.php' ) ) {
		throw new Exception ( "PHPMailer library file not found" );
	}



	require_once ( $this->phpMailerPath . 'class.phpmailer.php' );


	$mail = new PHPMailer ( );

	

	if ( $this->_bUseSMTP ) {

		$mail->IsSMTP ();
		if ( $this->_sSmtpSecure != '' ) {
			$mail->SMTPSecure = $this->_sSmtpSecure;
		}

		$mail->SMTPAuth = TRUE;

		$mail->SMTPKeepAlive = TRUE;

		$mail->Host = $this->_sSmtpHost;

		$mail->Port = $this->_sSmtpPort;

		$mail->Username = $this->_sSmtpUser;

		$mail->Password = $this->_sSmtpPassword;

	}

	

	$mail->SetFrom ( $this->_sFromEmail, $this->_sFromName );

	

	if ( $this->_sReplyToEmail != '' && $this->isValidEmail ( $this->_sReplyToEmail ) ) {

		$mail->AddReplyTo ( $this->_sReplyToEmail, $this->_sReplyToName );

	}

	

	$mail->Subject = $subject;

	

	foreach ( $this->_aRecipients as $aRecipient ) {

		//	use generic or a custom method which extracts the text from html. Default: generic message

		$mail->AltBody = $this->sAltMessage;

		//	sets the body, also replace tokens and other recipient defined search/replace operations

		$mail->MsgHTML ( $this->processTokens ( $message, $aRecipient [ 'search' ], $aRecipient [ 'replace' ] ) );

		$mail->AddAddress ( $aRecipient [ "email" ], $aRecipient [ "name" ] );

		

		foreach ( $this->_aAttachments as $aAttachment ) {

			switch ( $aAttachment [ 'type' ] ) {

				case 'inline' :

					$mail->AddStringAttachment ( $aAttachment [ "path" ] );

					break;

				default :

				case 'attachment' :

					$mail->AddAttachment ( $aAttachment [ "path" ] );

					break;

			}

		

		}



		if ( $mail->Send () ) {

			$this->_iSent ++;

		}

		else {

			$this->_iFailed ++;

			//	log the failed email and error. We may use this list to clean our mailing list

			$this->logError ( $aRecipient [ "email" ], $mail->ErrorInfo );

		}

		

		// Clear all addresses and attachments for next loop

		$mail->ClearAddresses ();

		$mail->ClearAttachments ();

	}

}



The above function is the brain of our mailing system since it gathers everything from the other class methods and sends the emails out. We now have a powerful mailing system which is intelligent enough to serve as our base emailing system across applications since it's built with minimum requirements from external applications and it's easy to implement in every project. It can handle huge amounts of mailing lists, can add unlimited attachments both inline or attached to the message, logs the errors so you can have a small report of failed recipients and, maybe, clean your list, able to send personalized messages for your recipients and many other things...all in a simple implementation.

We have the brain ready to process our emails so it's time to move to the little methods which prepares everything. It's time to learn how to add the recipients. We use a private peoperty '$_aRecipients' to hold the array until it's requested by the 'send' function above. Our method is pretty simple and similar to all the other ones in this class since their goal is to set and hold everything until requested.

public function addRecipient ( $email, $name = FALSE, $search = array (), $replace = array () ) {

	if ( $this->isValidEmail ( $email ) ) {

		$this->_aRecipients [] = array ( 

			'email' => $email, 

			'name' => ( $name ) ? $name : $email, 

			'search' => $search, 

			'replace' => $replace 

		);

	}

	else {

		if ( $this->bThrowExceptions ) {

			throw new Exception ( "Email $email is not a valid recipient" );

		}

	}

}


So we store a name, email and two other variables for tokens which should allow us to personalize the emails. This is very important because most people (including me) really use this feature which tends to increase the open rate of the emails. For example an email which addresses the recipient with 'Hi John' instead of just 'Hi' might have a bigger success when it comes to open rates and other very important statistics. Both the search and replace variables should be able to process strings or arrays if you want to add more than one.

Our 'addRecipient' simply adds everything to an array, it does not replaces the tokens at this stage. The tokens are replaced when we loop through our list of emails to prepare and personalize the message for each recipient in part. Responsible for this action is the 'processTokens' method which only accepts the original message as a parameter and the search-replace variables. It outputs the final message, parsed and ready for delivery.

private function processTokens ( $message, $search, $replace ) {
	if ( ( is_array ( $search ) && ! is_array ( $replace ) ) || ( is_array ( $replace ) && ! is_array ( $search ) ) || count ( $replace ) != count ( $search ) ) {
		if ( $this->bThrowExceptions ) {

			throw new Exception ( "Tokens are not well formatted" );

		}
		return $message;
	}

	if ( is_array ( $search ) ) {
		ksort ( $search );
		ksort ( $replace );

		foreach ( $search as $k => $v ) {
			$search [ $k ] = '/' . preg_quote ( $v ) . '/';
		}
		return preg_replace ( $search, $replace, $message );
	}
	return preg_replace ( '/' . preg_quote ( $search ) . '/', $replace, $message );
}


The rest of our class methods are pretty much the same so there's no point in sharing the same movie but with different actors.

You will notice that most of our class properties are private or protected. Object oriented programming principles encourage encapsulation, i.e. containing all the functionality inside a class. Usually this means member variables should be private or protected, one of the common ways to do this is with various set and get methods like we did in our class.

Since I like offering eevrything discussed in small downloadable packages at the end make sure you download our full working example and play with it.
email.zip Read more ...
1 2 ► LAST
Home
© 2008 drSoft Ltd. All rights reserved.