Monday 19 March 2012

My PEAR Net_SMTP function for PHP

OK, so some time ago I had an issue that I needed to send multipart/mime email through an authenticated SMTP server.  Had I been using a Linux distro this wouldn't have been a problem, as I could have simply added the authentication information into the relevant .ini files and that would have let the PHP mail()connect and send.  However I am normally coding for Windows environments so needed another solution.  I had a poke about on Google, as you do, and found that there were a fair few third party libraries that were out there, under differing license models, that would do the job.  Nothing was available as an inherent function in PHP.  Resigned that I was going to have to install something new, I opted for the PEAR NetSMTP class. This was because I already had the base components of PEAR installed on the system, so all I had to do was grab the package and its dependencies and away I could go.   And so started a three day fight to get the damn thing to send a mail.

What I found to be of particular detriment to my cause was that there was so little documentation for the class, and in particular its use, that it was a full on trial and error experience.  As a result I had to ask several questions on phpfreaks.com and, with some help on there I got the thing working as I needed it to.  Having spent as long as I did trying to make it all work as I wanted it to, I thought I would create a function for others to use to make life easier for those in a similar position.  So I wrote the following:

<?phprequire_once 'Net/SMTP.php'//stick this line somewhere at the top of your page
/*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*
I wrote this function to use the PEAR Net_SMTP class as I needed something that
would pass SMTP authentication information, and didn't want to install any new
librarys.  The most important parts of this were getting the right line terminator
and then getting the right number of them for each line!
Feel free to tweek this as much as you like, just don't change any of the line
ends or your fixing it on your own :D
--This is NOT configured to work with SSL or TSL (so no sending through gmail)
Any questions you can get in touch at:
muddyfunster.webdev@gmail.com
*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*/

 function sendSMTP($subject$body$user='defaultUserName'$pass='defaultPass'$to='Friendly Name'$destination='default@recipient.ext'$from='Friendly Name'$sender='default@address.ext'$fName=null$attachment=null){ 
//set defaults for user name, password, to, from, sender and recipient here ^^^
$e "\n"; //define end of line 
$bound"==MUDDY_FUNSTER_".md5(time()); //create a unique boundary for this mail to separate each different type in the message.
 @$smtp = new Net_SMTP("your.mailserver.ext"25); // set to youe mail providers A record and port number (IP Addresses will also work for server) 
@$smtp->connect(60); //create connection and set timeout duration 
@$smtp->helo('your_mail_domain.ext'); //start comunication with server --change to your mail providers domain    
@$smtp->auth($user$pass);  //authenticate account 
@$smtp->mailFrom("$sender");  //email address of authenticated account, can set default in function declaration
@$smtp->rcptTo("$destination");   //recipient of mail, can set default in function declaration 
$header "";  //set header as empty 
$header .= "From: $to <$sender>".$e// Set Friendly Name and address of sender
$header .= "To: $from <$destination>".$e// Set Friendly name and address of recipient
$header .= "Subject: $subject".$e;  //Set mail Subject 
$header .= "Reply-To: $sender".$e;  //Set Reply-to address 
$header .= 'MIME-Version:1.0'.$e;   //use MIME transport 
$header .= "Content-type:multipart/mixed;"// Define multipart mail contnent 
$header .= "boundary=\"".$bound."\"".$e.$e//set unique boundary for each different type of content 
$message $header//include headers into message information 
$message .= "--".$bound.$e//open boundary 
$message .= "Content-type:text/html;";  //set contnent as html for mail body
$message .= "charset=\"iso-8859-1\";".$e//set charactor set to be used in mail body - can be changed to suit 
$message .= "Content-disposition:inline;"//use inline to inferr displaying of contnet in the body without need for end user input 
$message .= "Content-transfer-encoding:quoted-printable".$e.$e.$e//set transfer encoding for html text content
$message .= $body.$e.$e//add the contnent of $body which is passed into the function
if($attachment != null && $fName != null){ //check if there is an attchment being passed into the function and that it has a name
$message .= "--".$bound.$e//add boundary to change to next content type
$message .= "Content-type:application/octet-stream;"//set content type to suitable type for attachment (octet-stream is for generic binary data other are available) 
$message .= "name=\"$fName.xml\"".$e//set the file name
 $message .= "Content-disposition:attachment;"//use attachment to infer data that is being passed as a file 
$message .= "filename=\"$fName.ext\";".$e//set the filename and extension as it will be recieved 
$message .= "Content-transfer-encoding:base64".$e.$e// encode the file for transfer
$message .= $attachment.$e.$e// add attachment contents 
} 
$message .= "--$bound--".$e//closing boundary shows it's the last part of the MIME and the mail is now complete 
@$smtp->data($message); //send message to the server 
$msg $smtp->getResponse(); // get server response array format -- $msg = Array([0]=>'server numeric code' [1]=>'Text message from server for code') -- hMailServer success response code is 250.  This can be called after any of the $smtp-> calls for error capture
@$smtp->disconnect(); // close server connection
 return $msg//pass out server response array
 }                     
 //call function using $var = sendSMTP($subject, $body, $user, $pass, $to, $recipientAddress, $from, $senderAddress, $filenameOfAttachment, $attachmentContent);
//$to and $from are the "Friendly Name" values for the message e.g. John Doe
//if you are passing in attachment content you MUST pass in a file name (prefferably with extension, but thats your targets problem) for it as well.

 ?>

The formatting has been a little upset posting the code up like this, so if you have any issues copying it you can find the same code at the end of my thread on phpfreaks.com here.

To use this function you must have PEAR Net_SMTP installed and up to date.  Once that's done just copy this code and save it in either a new .php file and load it using include_once or paste it into your existing include files/current page.

This function lets you send HTML formatted messages with or without an attachment.  The other thing is that it is written to pass in attachments on the fly, so there is no need to have an existing file saved on the server to attach (I wrote it this way as my original code was generating xml file attachments from a database lookup, so I really didn't want a bunch of files littering the server when I already had the information in the database anyway).

You will also see a whole bunch of @ suppressors in the function.  I normally avoid using these, but something in the way the Net_SMTP class accesses the PEAR error handling class keeps throwing warnings, even though there isn't an issue with the actual function or it's coding, it's just the relationship between the SMTP and PEAR error classes that's a bit out.

I have tested this using hMail free mail server and it worked a treat.  The last few lines of comments explain how to call the function in your code and how to read the server response array that the function returns.

Another thing is the function declaration has the option for default values, you will obviously need to change these if you want to use them.  I do feel that I should point out, this sends mail as a fully authorised account on whatever mail server you hook it up to, thus any spam sent from this will be traced to your mail server, not just the script, so you would probably want access controls in place if you choose to use all default values.

Enjoy, and let me know if you have any problems with this.

Friday 16 March 2012

SQL Staring Blocks - A basic look at punctuation and JOIN

SQL uses some syntax that can be confusing at first glance.  Let's look at this really basic example:

SELECT table1.`first name`, table2.city
FROM table1 LEFT JOIN table2
ON (table1.id = table2.user_id)
WHERE table2.city = "My Home Town";

Here you can see as well as the key words that are in block capitals there is some punctuation throughout the statement.  This has a function.  Starting with the comma, that's just what you would expect, it separates items in a list.  Next the full stop (or period if you prefer) this is slightly different, where you see oneThing.secondThing this is telling the database that the you want to access the sub-component of oneThing that is called secondThing.  What I mean by that is easier taken in context of your basic windows file system layout:  You have your My Documents folder, within that you have a folder for all your text files call Text Docs and within that you would have a file called MyFile.txt.  For the file system to access this it runs down through the folders until it gets to the file and would end up looking at My Documents\Text Docs\MyFile.txt.  The way a database system looks at it is just the same, only the \ are replaced by .
So if we change it up so that what we have is a database called My Documents, a table within that database called Text Docs and a column named MyFile in that table that we want to query the information on we would access that bit of information by using
SELECT `My Documents`.`Text Docs`.MyFile

You will only need to put the name of the database that the table is in into your query if it is a different database from the one that you are running the query in and if you also need to access information from the database that you are currently in as well.  If you only need to use a single database always run the query from within that database - to do this use the USE command i.e.
USE databaseOne;

The next thing that you will notice regarding the use of punctuation marks is the backtick around some of the names of things ( ` ) This is NOT a single quote and does not do what a single quote does.  A single quote in SQL tells the database that you have a string of characters which, as far as it is concerned, are not related to anything in the makeup of the database.  The backtick however tells the database that, while the contents are still a string, the contents have a relation to something that makes up the database structure, such as a table name, a field name or a database name and are required if any of these names have a space in them.
-This is only a very basic explanation of the actual functionality of backticks so as to keep it as clear as I can.

Using backticks also lets you use queries on fields that you have named with one of the "Reserved Words" in your database.  You should always take care to avoid using reserved words when naming tables and columns in your database, but sometimes one creeps in.  If this happens you can use backticks around the name to tell the database not to treat the word as it normally would.

Something else that can be quite confusing the the use of JOIN's in SQL syntax.  JOIN's always go in the FROM part of your query and let you perform queries on multiple tables at the same time.  there are a couple of different joins and the exact syntax for how to call each one depends on which database you are running your query on.  For basic MySQL JOIN's you would use
FROM table1 {XXXX} JOIN table2 ON (table1.commonField = table2.matchingField)

You would replace the {XXXX} with either INNER, LEFT or RIGHT depending on how you want to join your two tables.  You must also join the tables on common fields that have a value that can be matched on each side of the join (or else there is no point in joining them).

INNER JOIN is used to create a 1-1 relationship between each record in each table.  If there is not a matching record in both the common and matched fields of table1 and table2, the record will be dropped from the dataset that your query returns.  This is usefull when you only want to find matching records between two tables:
SELECT table1.operator, table1.time, table2.location, table2.customerName
FROM table1 INNER JOIN table2 ON
(table1.jobID = table2.jobRefID)
WHERE table1.jobID = 100023

LEFT JOIN creates a 1-MANY relationship between the tables using the table on the left of the = in the ON clause as the "main" table and returning every record from the table on the right for every match that is found between the two tables, this is handy when you want you have what I call a "master and detail" table setup, where you have a "master" table that has limited records, such as product groups, and a "detail" table that has a lot of lines, such as a product description table, now say you want all the products and their descriptions that match a specific product group:
SELECT detail.productName, detail.productDescription
FROM master LEFT JOIN detail ON
(master.ID = detail.groupID)
WHERE master.ID = 3

RIGHT JOIN is the exact reverse of LEFT JOIN, creating a MANY-1 relationship.  RIGHT is generaly not used as much as LEFT because it's normaly easier for people to visualise the gathering of data using the 1-MANY model over the MANY-1.