49. Classi per inviare email

In questo capitolo costruiremo una libreria che ci consentirà di inviare email complesse, ad esempio in HTML con uno o più allegati, in modo molto trasparente e veloce.

Questa libreria non è completa anche se funziona correttamente. Non ho infatti implementato alcuna gestione delle eccezioni che vi consiglio pertanto di aggiungere qualora decidiate di usarla nello sviluppo di applicazioni reali.
Inoltre sarebbe stato utile interfacciare la libreria con la classe "File" vista nei capitoli precedenti, ma ho preferito comunque non inserire altre classi esterne essendo lo scopo di questa libreria puramente didattico.

Vediamo ora le Classi che compongono questa nuova libreria e il loro scopo :

  • MIME - Classe usata come contenitore di costanti per definire i tipi MIME supportati dall'applicazione
  • MailBlock - Rappresenta un sottoblocco del messaggio email, quest ultimo sarà quindi composto da una o più istanze di MailBlock
  • Allegato - Derivata da MailBlock, rappresenta un file allegato ossia un sottoblocco più complesso con codifica Base64
  • Email - Classe principale creata per fornire un'interfaccia fra tutte le altre classi, costruendo il messaggio email completo e inviandolo

Classe : "MIME"
Nome file : "MIME.php"

<?php

	class MIME
	{
		const HTML = "text/html";
		const TEXT = "text/plain";
		const MULTI = "multipart/mixed";

		const GIF = "image/gif";
		const JPEG = "image/jpeg";
		const PNG = "image/png";
		const PSD = "image/psd";
		const BMP = "image/bmp";
		const TIFF = "image/tiff";
		const FLASH = "application/x-shockwave-flash";
	}

?>

Classe : "MailBlock"
Nome file : "MailBlock.php"

<?php

	define("EOL", "\r\n");

	class MailBlock
	{
		protected $content_type;
		protected $charset;
		protected $content_transfer_encoding;

		public $content;
		protected $boundary;

		public function MailBlock($content_type, $boundary, $content, $charset = "iso-8859-1", $c_t_encoding = "8bit")
		{
			$this->content_type = $content_type;
			$this->charset = $charset;
			$this->content_transfer_encoding = $c_t_encoding;

			$this->content = $content;
			$this->boundary = $boundary;
		}

		public function __toString()
		{
			$content = "--" . $this->boundary . EOL;
			$content .= "Content-Type: " . $this->content_type . "; charset=" . $this->charset . EOL;
			$content .= "Content-Transfer-Encoding: " . $this->content_transfer_encoding . EOL;
			$content .= $this->content . EOL;
			$content .= "--" . $this->boundary . EOL;

			return $content;
		}
	}

?>

La classe proposta è piuttosto semplice, non fa altro che collezionare i dati necessari a rappresentare un sottoblocco e ad organizzarli attraverso il metodo __toString().
Il codice che segue inizializza correttamente un sottoblocco in formato HTML :

<?php

	require_once("MIME.php");
	require_once("MailBlock.php");

	$boundary = "fa0s7u98hg87ngf0hgk05695j";
	$contenuto = "<ul><li>Linea uno</li><li>Linea due</li></ul>";

	$blocco = new MailBlock(MIME::HTML, $boundary, $contenuto);

	echo $blocco; // Richiama "__toString()" stampando il sottoblocco con la sintassi corretta

?>

Classe : "Allegato"
Nome file : "Allegato.php"

<?php

	require_once("MailBlock.php");

	class Allegato extends MailBlock
	{
		public $url;
		public $name;
		public $description;

		public function Allegato($name, $url, $content_type, $boundary, $description = NULL)
		{
			$this->url = $url;
			$this->name = $name;
			$this->boundary = $boundary;
			$this->description = $description;

			$this->content = $this->leggi();
			parent::MailBlock($content_type, $boundary, $this->content, "utf-8", "base64");
		}

		private function leggi()
		{
			$file = @fopen($this->url, "r");
			$allegato = fread($file, filesize($this->url));
			return base64_encode($allegato);
		}

		public function __toString()
		{
			$content = "--" . $this->boundary . EOL;
			$content .= "Content-Type: " . $this->content_type . "; name=\"" . $this->name . "\"" . EOL;
			$content .= "Content-Transfer-Encoding: " . $this->content_transfer_encoding . EOL;
			$content .= "Content-Description: " . $this->description . EOL;
			$content .= "Content-Disposition: attachment; filename=\"" . $this->name . "\"" . EOL;
			$content .= $this->content . EOL;
			$content .= "--" . $this->boundary . EOL;

			return $content;
		}
	}

?>

La classe soprastante deriva dalla precendente MailBlock, aggiungendo però tre importanti parametri necessari per gestire il file allegato, ossia l'url, il nome del file che sarà visualizzato dal destinatario e infine una descrizione facoltativa.

Il metodo privato leggi() si occupa invece di leggere il file specificato, per poi inserirlo nel sottoblocco in questione usando la codifica Base64.

Il metodo pubblico __toString() si comporta come il suo genitore, organizzando però il sottoblocco con i tre parametri aggiuntivi e inserendo alla fine il contenuto del file specificato.

Nel prossimo capitolo vedremo l'oggetto Email, l'ultima classe della nostra libreria con del codice d'esempio.

In questa lezione vedremo la classe Email, analizzandone brevemente metodi e attributi ed infine un esempio per l'utilizzo della nuova libreria con l'invio di due allegati.

Attributi

  • private $to_mail - Array contenente la lista dei destinatari.
  • private $object - Oggetto dell'email.
  • private $message - Array contenente tutti i sottoblocchi che formeranno il messaggio, ossia istanze di "MailBlock" e "Allegato".
  • public $mime - Versione MIME utilizzata per lo standard dell'email.
  • public $content_type - Content-Type per identificare il contenuto del messaggio (es. "text/html" oppure "multipart/mixed" ...).
  • private $boundary - Delimitatore boundary generato criptando il timestamp Unix corrente.
  • public $cc - Stringa contenente gli indirizzi per l'invio dell'email in copia carbone. Consiglio di rendere private l'attributo e di trasformarlo in un array, costruendo un metodo per un inserimento controllato.
  • public $bcc - Stringa contenente gli indirizzi per l'invio dell'email in copia carbone. Anche qui consiglio di agire come con l'attributo $cc.
  • public $date - Data di invio email (tipo stringa).
  • public $from - Indirizzo email del mittente, consiglio anche qui di effettuare la stessa modifica descritta per l'attributo $cc.
  • public $reply_to - Specifica l'email predefinita a cui il destinatario dell'email potrà rispondere. Anche qui stesso consiglio.
  • public $xmailer - Stringa che identifica il software utilizzato per l'invio dell'email.

Metodi

  • public Email() - Costruttore della classe. Inizializza $object, $content_type e altri attributi.
  • public blocco() - Aggiunge un sottoblocco standard al corpo del messaggio, ossia un'istanza MailBlock.
  • public allegato() - Aggiunge un sottoblocco più complesso al corpo del messaggio che rappresenterà un file allegato, ossia un'istanza di Allegato.
  • public destinatario() - Aggiunge un indirizzo alla lista dei destinatari.
  • private header() - Metodo privato che costruisce l'header dell'email basandosi sui parametri memorizzati negli attributi della classe.
  • public invia() - Metodo per l'invio dell'email. Restituisce true o false per identificare se l'invio è avvenuto con successo.

Classe : "Email"
Nome file : "Email.php"

<?php

	require_once("MIME.php");
	require_once("Allegato.php");

	class Email
	{
		private $to_mail;
		private $object;
		private $message;

		public $mime = "1.0";
		public $content_type;
		private $boundary = NULL;

		public $cc = NULL;
		public $bcc = NULL;
		public $date = NULL;
		public $from = NULL;
		public $replyto = NULL;
		public $xmailer = NULL;

		public function Email($object, $content_type = MIME::TEXT)
		{
			$this->to_mail = array();
			$this->object = $object;
			$this->message = array();

			$this->boundary = md5(time());
			$this->content_type = $content_type;
		}

		public function blocco($content_type, $content, $charset = "iso-8859-1", $c_t_encoding = "8bit")
		{
			$succ = count($this->message);
			$this->message[$succ] = new MailBlock($content_type, $this->boundary, $content, $charset, $c_t_encoding);
		}

		public function allegato($name, $url, $mime_type, $description = NULL)
		{
			$succ = count($this->message);
			$this->message[$succ] = new Allegato($name, $url, $mime_type, $this->boundary, $description);

			/* Se questo metodo viene richiamato significa che è stato inserito almeno un
			allegato quindi per sicurezza modifico il Content-Type a "multipart/mixed" */
			$this->content_type = MIME::MULTI;
		}

		public function destinatario($to_mail)
		{
			array_push($this->to_mail, $to_mail);
		}

		private function header()
		{
			$header = "MIME-Version: " . $this->mime . EOL;
			$header .= "Content-Type: " . $this->content_type . "; boundary=\"" . $this->boundary . "\"" . EOL;
			$header .= "Content-Transfer-Encoding: 8bit" . EOL;

			if ($this->from != NULL) { $header .= "From: " . $this->from . EOL; }
			if ($this->replyto != NULL) { $header .= "Reply-To: " . $this->replyto . EOL; }
			if ($this->cc != NULL) { $header .= "Cc: " . $this->cc . EOL; }
			if ($this->bcc != NULL) { $header .= "Bcc: " . $this->bcc . EOL; }
			if ($this->date != NULL) { $header .= "Date: " . $this->date . EOL; }
			if ($this->xmailer != NULL) { $header .= "X-Mailer: " . $this->xmailer; }

			return $header;
		}

		public function invia()
		{
			$message = "";
			$blocchi = count($this->message);

			for ($i = 0; $i < $blocchi; $i++)
				$message .= $this->message[$i]; // Richiama il metodo __toString() di "Allegato" o "MailBlock"

			$to = implode(", ", $this->to_mail);
			return mail($to, $this->object, $message, $this->header());
		}
	}

?>

Come potete vedere anche la classe Email è piuttosto semplice dovendo principalmente fungere da interfaccia unendo i dati.

Con questa classe la nostra libreria è al completo. Per ultimo vedremo un piccolo foglio PHP come esempio di utilizzo della libreria, inviando un'email con un blocco di testo, un blocco in HTML e 2 file allegati.

test.php

<?php

	require_once("Email.php");

	$mail = new Email("Mail con allegati", MIME::MULTI);

	/* Codice per aggiungere i destinatari */
	$mail->destinatario("RS Staff <posta@realizzazione-sito.info>");
	//$mail->destinatario("Utente1 <utente1@dominio.it>");
	//$mail->destinatario("Utente2 <utente2@dominio.it>");

	$mail->from = "Mittente <email_di_invio@gmail.com>";
	$mail->replyto = "Risposta <email_per_risposta@gmail.com>";

	$mail->blocco(MIME::TEXT, "Iniziamo con un pò di testo nel primo blocco!!");

	/* Memorizzo l'html per il secondo blocco in una
	variabile per favorire la leggibilità del codice */
	$html = "<ul><li>Linea uno</li><li>Linea due</li></ul>";
	$html .= "<table border=\"1\"><tr><td>topolino<td><td>minnie</td></tr></table>";

	$mail->blocco(MIME::HTML, $html);
	$mail->allegato("litfiba.jpg", "allegati/file.jpg", MIME::JPEG);
	$mail->allegato("220 volts.jpg", "allegati/220v.jpg", MIME::JPEG);

	$inviata = $mail->invia();

	if ($inviata) { echo "Mail inviata con successo!!"; }
	else { echo "Mail non inviata!!"; }

?>

Il risultato ottenuto.

Anche in questo caso ho testato il codice solo con GMail, non escludo quindi problemi di compatibilità con altri siti o client di posta (Outlook, Eudora ...), e dovendo riscontrarne vi consiglio di provare a modificare l'EOL o il boundary di chiusura.