Zend_Mail puede leer mensajes de correo desde varios almacenamientos
de correo locales o remotos. Todos ellos tienen la misma API básica
para contar y obtener mensajes, y algunos de ellos implementan interfaces adicionales para
características menos comunes. Para ver un resumen de características de los almacenamientos
implementados, consulte la siguiente tabla.
Tabla 45.1. Resumen de características de lectura de correo
| Característica | Mbox | Maildir | Pop3 | IMAP |
|---|---|---|---|---|
| Tipo de almacenamiento | local | local | remoto | remoto |
| Obtener mensaje | Sí | Sí | Sí | Sí |
| Obtener parte MIME | emulado | emulado | emulado | emulado |
| Carpetas | Sí | Sí | No | Sí |
| Crear mensaje/carpeta | No | pendiente | No | pendiente |
| Indicadores | No | Sí | No | Sí |
| Cuota | No | Sí | No | No |
$mail = new Zend_Mail_Storage_Pop3(array('host' => 'localhost',
'user' => 'test',
'password' => 'test'));
echo $mail->countMessages() . " messages found\n";
foreach ($mail as $message) {
echo "Mail from '{$message->from}': {$message->subject}\n";
}
Mbox y Maildir son los dos formatos soportados para almacenamientos de correo locales, ambos en sus formas más simples.
Si desea leer desde un archivo Mbox solo necesita indicar el nombre del archivo al
constructor de Zend_Mail_Storage_Mbox:
$mail = new Zend_Mail_Storage_Mbox(array('filename' =>
'/home/test/mail/inbox'));
Maildir es muy similar pero necesita un dirname:
$mail = new Zend_Mail_Storage_Maildir(array('dirname' =>
'/home/test/mail/'));
Ambos constructores lanzan una Zend_Mail_Exception si el
almacenamiento no puede ser leído.
Para almacenamientos remotos se soportan los dos protocolos más populares: Pop3 e Imap. Ambos necesitan al menos un host y un usuario para conectarse e iniciar sesión. La contraseña por defecto es una cadena vacía, y el puerto por defecto es el indicado en el RFC del protocolo.
// connecting with Pop3
$mail = new Zend_Mail_Storage_Pop3(array('host' => 'example.com',
'user' => 'test',
'password' => 'test'));
// connecting with Imap
$mail = new Zend_Mail_Storage_Imap(array('host' => 'example.com',
'user' => 'test',
'password' => 'test'));
// example for a none standard port
$mail = new Zend_Mail_Storage_Pop3(array('host' => 'example.com',
'port' => 1120
'user' => 'test',
'password' => 'test'));
Para ambos almacenamientos se soportan SSL y TLS. Si utiliza SSL el puerto por defecto cambia según lo indicado en el RFC.
// examples for Zend_Mail_Storage_Pop3, same works for Zend_Mail_Storage_Imap
// use SSL on different port (default is 995 for Pop3 and 993 for Imap)
$mail = new Zend_Mail_Storage_Pop3(array('host' => 'example.com',
'user' => 'test',
'password' => 'test',
'ssl' => 'SSL'));
// use TLS
$mail = new Zend_Mail_Storage_Pop3(array('host' => 'example.com',
'user' => 'test',
'password' => 'test',
'ssl' => 'TLS'));
Ambos constructores pueden lanzar Zend_Mail_Exception o
Zend_Mail_Protocol_Exception (que extiende
Zend_Mail_Exception), según el tipo de error.
Los mensajes pueden obtenerse después de haber abierto el almacenamiento. Necesita el
número de mensaje, que es un contador que comienza en 1 para el primer mensaje. Para
obtener el mensaje, se utiliza el método getMessage():
$message = $mail->getMessage($messageNum);
El acceso mediante array también está soportado, pero este método de acceso no admitirá
ningún parámetro adicional que pudiera añadirse a getMessage().
Mientras no le importe y pueda vivir con los valores por defecto, puede usar:
$message = $mail[$messageNum];
Para iterar sobre todos los mensajes se implementa la interfaz Iterator:
foreach ($mail as $messageNum => $message) {
// do stuff ...
}
Para contar los mensajes en el almacenamiento, puede usar el método
countMessages() o el acceso mediante array:
// method $maxMessage = $mail->countMessages(); // array access $maxMessage = count($mail);
Para eliminar un correo, se utiliza el método removeMessage() o,
de nuevo, el acceso mediante array:
// method $mail->removeMessage($messageNum); // array access unset($mail[$messageNum]);
Después de obtener los mensajes con getMessage() querrá
obtener las cabeceras, el contenido o partes individuales de un mensaje multiparte. Se
puede acceder a todas las cabeceras mediante propiedades o el método
getHeader() si desea más control o tiene nombres de cabecera
inusuales. Los nombres de cabecera se convierten internamente a minúsculas, por lo que la
capitalización del nombre de la cabecera en el mensaje de correo no importa. Además, las
cabeceras con un guion pueden escribirse en camel-case. Si no se encuentra ninguna
cabecera para ninguna de las dos notaciones se lanza una excepción. Para evitar esto se
puede usar el método headerExists() para comprobar la
existencia de una cabecera.
// get the message object
$message = $mail->getMessage(1);
// output subject of message
echo $message->subject . "\n";
// get content-type header
$type = $message->contentType;
// check if CC isset:
if( isset($message->cc) ) { // or $message->headerExists('cc');
$cc = $message->cc;
}
Si tiene varias cabeceras con el mismo nombre -por ejemplo, las cabeceras Received-
podría querer un array en lugar de una cadena. En este caso, utilice el método
getHeader().
// get header as property - the result is always a string,
// with new lines between the single occurrences in the message
$received = $message->received;
// the same via getHeader() method
$received = $message->getHeader('received', 'string');
// better an array with a single entry for every occurrences
$received = $message->getHeader('received', 'array');
foreach ($received as $line) {
// do stuff
}
// if you don't define a format you'll get the internal representation
// (string for single headers, array for multiple)
$received = $message->getHeader('received');
if (is_string($received)) {
// only one received header found in message
}
El método getHeaders() devuelve todas las cabeceras como un array
con el nombre en minúsculas como clave y el valor como un array para cabeceras múltiples o
como una cadena para cabeceras únicas.
// dump all headers
foreach ($message->getHeaders() as $name => $value) {
if (is_string($value)) {
echo "$name: $value\n";
continue;
}
foreach ($value as $entry) {
echo "$name: $entry\n";
}
}
Si no tiene un mensaje multiparte, obtener el contenido es fácil mediante
getContent(). A diferencia de las cabeceras, el contenido solo se
obtiene cuando se necesita (obtención tardía).
// output message content for HTML echo '<pre>'; echo $message->getContent(); echo '</pre>';
Comprobar si un mensaje es multiparte se hace con el método
isMultipart(). Si tiene un mensaje multiparte puede obtener una
instancia de Zend_Mail_Part con el método
getPart(). Zend_Mail_Part es la clase
base de Zend_Mail_Message, por lo que tiene los mismos métodos:
getHeader(), getHeaders(),
getContent(), getPart(),
isMultipart() y las propiedades para las cabeceras.
// get the first none multipart part
$part = $message;
while ($part->isMultipart()) {
$part = $message->getPart(1);
}
echo 'Type of this part is ' . strtok($part->contentType, ';') . "\n";
echo "Content:\n";
echo $part->getContent();
Zend_Mail_Part también implementa
RecursiveIterator, lo que facilita recorrer todas las partes.
Y para facilitar la salida, también implementa el método mágico
__toString(), que devuelve el contenido.
// output first text/plain part
$foundPart = null;
foreach (new RecursiveIteratorIterator($mail->getMessage(1)) as $part) {
try {
if (strtok($part->contentType, ';') == 'text/plain') {
$foundPart = $part;
break;
}
} catch (Zend_Mail_Exception $e) {
// ignore
}
}
if (!$foundPart) {
echo 'no plain text part found';
} else {
echo "plain text part: \n" . $foundPart;
}
Maildir e IMAP soportan el almacenamiento de indicadores (flags). La clase
Zend_Mail_Storage tiene constantes para todos los indicadores de
sistema conocidos de maildir e IMAP, llamados
Zend_Mail_Storage::FLAG_<flagname>. Para comprobar
indicadores, Zend_Mail_Message tiene un método llamado
hasFlag(). Con getFlags() obtendrá todos
los indicadores establecidos.
// find unread messages
echo "Unread mails:\n";
foreach ($mail as $message) {
if ($message->hasFlag(Zend_Mail_Storage::FLAG_SEEN)) {
continue;
}
// mark recent/new mails
if ($message->hasFlag(Zend_Mail_Storage::FLAG_RECENT)) {
echo '! ';
} else {
echo ' ';
}
echo $message->subject . "\n";
}
// check for known flags
$flags = $message->getFlags();
echo "Message is flagged as: ";
foreach ($flags as $flag) {
switch ($flag) {
case Zend_Mail_Storage::FLAG_ANSWERED:
echo 'Answered ';
break;
case Zend_Mail_Storage::FLAG_FLAGGED:
echo 'Flagged ';
break;
// ...
// check for other flags
// ...
default:
echo $flag . '(unknown flag) ';
}
}
Como IMAP permite indicadores definidos por el usuario o el cliente, podría obtener
indicadores que no tengan una constante en Zend_Mail_Storage. En
su lugar, se devuelven como cadenas y pueden comprobarse de la misma forma con
hasFlag().
// check message for client defined flags $IsSpam, $SpamTested
if (!$message->hasFlag('$SpamTested')) {
echo 'message has not been tested for spam';
} else if ($message->hasFlag('$IsSpam')) {
echo 'this message is spam';
} else {
echo 'this message is ham';
}
Todos los almacenamientos, excepto Pop3, soportan carpetas, también llamadas buzones. La
interfaz implementada por todos los almacenamientos que soportan carpetas se llama
Zend_Mail_Storage_Folder_Interface. Además, todas estas clases
tienen un parámetro opcional adicional llamado folder, que es la
carpeta seleccionada después de iniciar sesión, en el constructor.
Para los almacenamientos locales necesita usar clases separadas llamadas
Zend_Mail_Storage_Folder_Mbox o
Zend_Mail_Storage_Folder_Maildir. Ambas necesitan un parámetro
llamado dirname con el nombre del directorio base. El formato para
maildir es el definido en maildir++ (con un punto como delimitador por defecto), Mbox es
una jerarquía de directorios con archivos Mbox. Si no tiene un archivo Mbox llamado INBOX
en su directorio base Mbox necesita establecer otra carpeta en el constructor.
Zend_Mail_Storage_Imap ya soporta carpetas por defecto. Ejemplos
para abrir estos almacenamientos:
// mbox with folders
$mail = new Zend_Mail_Storage_Folder_Mbox(array('dirname' =>
'/home/test/mail/'));
// mbox with a default folder not called INBOX, also works
// with Zend_Mail_Storage_Folder_Maildir and Zend_Mail_Storage_Folder_Imap
$mail = new Zend_Mail_Storage_Folder_Mbox(array('dirname' =>
'/home/test/mail/',
'folder' =>
'Archive'));
// maildir with folders
$mail = new Zend_Mail_Storage_Folder_Maildir(array('dirname' =>
'/home/test/mail/'));
// maildir with colon as delimiter, as suggested in Maildir++
$mail = new Zend_Mail_Storage_Folder_Maildir(array('dirname' =>
'/home/test/mail/',
'delim' => ':'));
// imap is the same with and without folders
$mail = new Zend_Mail_Storage_Imap(array('host' => 'example.com',
'user' => 'test',
'password' => 'test'));
Con el método getFolders($root = null) puede obtener la jerarquía de carpetas comenzando
en la carpeta raíz o en la carpeta indicada. Se devuelve como una instancia de
Zend_Mail_Storage_Folder, que implementa
RecursiveIterator, y todos los hijos son también instancias de
Zend_Mail_Storage_Folder. Cada una de estas instancias tiene un
nombre local y un nombre global, devueltos por los métodos
getLocalName() y getGlobalName(). El
nombre global es el nombre absoluto desde la carpeta raíz (incluyendo delimitadores), el
nombre local es el nombre dentro de la carpeta padre.
Tabla 45.2. Nombres de carpetas de correo
| Nombre global | Nombre local |
|---|---|
| /INBOX | INBOX |
| /Archive/2005 | 2005 |
| List.ZF.General | General |
Si utiliza el iterador, la clave del elemento actual es el nombre local. El nombre global
también se devuelve mediante el método mágico __toString().
Algunas carpetas pueden no ser seleccionables, lo que significa que no pueden almacenar
mensajes y seleccionarlas provoca un error. Esto puede comprobarse con el método
isSelectable(). Así resulta muy fácil mostrar todo el árbol en
una vista:
$folders = new RecursiveIteratorIterator($this->mail->getFolders(),
RecursiveIteratorIterator::SELF_FIRST);
echo '<select name="folder">';
foreach ($folders as $localName => $folder) {
$localName = str_pad('', $folders->getDepth(), '-', STR_PAD_LEFT) .
$localName;
echo '<option';
if (!$folder->isSelectable()) {
echo ' disabled="disabled"';
}
echo ' value="' . htmlspecialchars($folder) . '">'
. htmlspecialchars($localName) . '</option>';
}
echo '</select>';
La carpeta actualmente seleccionada se devuelve mediante el método
getCurrentFolder(). Cambiar de carpeta se hace con el método
selectFolder(), que necesita el nombre global como parámetro.
Si desea evitar escribir delimitadores también puede usar las propiedades de una
instancia de Zend_Mail_Storage_Folder:
// depending on your mail storage and its settings $rootFolder->Archive->2005 // is the same as: // /Archive/2005 // Archive:2005 // INBOX.Archive.2005 // ... $folder = $mail->getFolders()->Archive->2005; echo 'Last folder was ' . $mail->getCurrentFolder() . "new folder is $folder\n"; $mail->selectFolder($folder);
Si está usando un almacenamiento remoto y tiene tareas largas, podría necesitar mantener la conexión activa mediante noop:
foreach ($mail as $message) {
// do some calculations ...
$mail->noop(); // keep alive
// do something else ...
$mail->noop(); // keep alive
}
Zend_Mail_Storage_Mbox,
Zend_Mail_Storage_Folder_Mbox,
Zend_Mail_Storage_Maildir y
Zend_Mail_Storage_Folder_Maildir implementan los métodos
mágicos __sleep() y __wakeup(), lo
que significa que son serializables. Esto evita analizar los archivos o el árbol de
directorios más de una vez. La desventaja es que su almacenamiento Mbox o Maildir no
debería cambiar. Se pueden realizar algunas comprobaciones sencillas, como volver a
analizar el archivo Mbox actual si la hora de modificación cambia, o volver a analizar
la estructura de carpetas si una carpeta ha desaparecido (lo que igualmente resulta en
un error, pero puede buscar otra carpeta después). Es mejor si tiene algo como un
archivo de señal para los cambios y lo comprueba antes de usar la instancia en caché.
// there's no specific cache handler/class used here,
// change the code to match your cache handler
$signal_file = '/home/test/.mail.last_change';
$mbox_basedir = '/home/test/mail/';
$cache_id = 'example mail cache ' . $mbox_basedir . $signal_file;
$cache = new Your_Cache_Class();
if (!$cache->isCached($cache_id) ||
filemtime($signal_file) > $cache->getMTime($cache_id)) {
$mail = new Zend_Mail_Storage_Folder_Pop3(array('dirname' =>
$mbox_basedir));
} else {
$mail = $cache->get($cache_id);
}
// do stuff ...
$cache->set($cache_id, $mail);
Los almacenamientos remotos usan dos clases:
Zend_Mail_Storage_<Name> y
Zend_Mail_Protocol_<Name>. La clase de protocolo
traduce los comandos y respuestas del protocolo desde y hacia PHP,
como métodos para los comandos o variables con diferentes estructuras para los datos.
La otra clase, la principal, implementa la interfaz común.
Si necesita características adicionales del protocolo, puede extender la clase de protocolo y usarla en el constructor de la clase principal. Como ejemplo, suponga que necesitamos golpear (knock) diferentes puertos antes de poder conectarnos a POP3.
class Example_Mail_Exception extends Zend_Mail_Exception
{
}
class Example_Mail_Protocol_Exception extends Zend_Mail_Protocol_Exception
{
}
class Example_Mail_Protocol_Pop3_Knock extends Zend_Mail_Protocol_Pop3
{
private $host, $port;
public function __construct($host, $port = null)
{
// no auto connect in this class
$this->host = $host;
$this->port = $port;
}
public function knock($port)
{
$sock = @fsockopen($this->host, $port);
if ($sock) {
fclose($sock);
}
}
public function connect($host = null, $port = null, $ssl = false)
{
if ($host === null) {
$host = $this->host;
}
if ($port === null) {
$port = $this->port;
}
parent::connect($host, $port);
}
}
class Example_Mail_Pop3_Knock extends Zend_Mail_Storage_Pop3
{
public function __construct(array $params)
{
// ... check $params here! ...
$protocol = new Example_Mail_Protocol_Pop3_Knock($params['host']);
// do our "special" thing
foreach ((array)$params['knock_ports'] as $port) {
$protocol->knock($port);
}
// get to correct state
$protocol->connect($params['host'], $params['port']);
$protocol->login($params['user'], $params['password']);
// initialize parent
parent::__construct($protocol);
}
}
$mail = new Example_Mail_Pop3_Knock(array('host' => 'localhost',
'user' => 'test',
'password' => 'test',
'knock_ports' =>
array(1101, 1105, 1111)));
Como puede ver, siempre asumimos que estamos conectados, con la sesión iniciada y, si se admite, con una carpeta seleccionada en el constructor de la clase principal. Por lo tanto, si asigna su propia clase de protocolo, siempre debe asegurarse de que eso se haga o el siguiente método fallará si el servidor no lo permite en el estado actual.
Zend_Mail_Storage_Writable_Maildir tiene soporte para cuotas
de Maildir++. Está deshabilitado por defecto, pero es posible usarlo manualmente, si
no se desean las comprobaciones automáticas (esto significa que
appendMessage(), removeMessage()
y copyMessage() no realizan comprobaciones ni añaden
entradas al archivo maildirsize). Si está habilitado, se lanza una excepción si intenta
escribir en el maildir y este ya está sobre la cuota.
Existen tres métodos usados para las cuotas: getQuota(),
setQuota() y checkQuota():
$mail = new Zend_Mail_Storage_Writable_Maildir(array('dirname' =>
'/home/test/mail/'));
$mail->setQuota(true); // true to enable, false to disable
echo 'Quota check is now ', $mail->getQuota() ? 'enabled' : 'disabled', "\n";
// check quota can be used even if quota checks are disabled
echo 'You are ', $mail->checkQuota() ? 'over quota' : 'not over quota', "\n";
checkQuota() también puede devolver una respuesta más
detallada:
$quota = $mail->checkQuota(true);
echo 'You are ', $quota['over_quota'] ? 'over quota' : 'not over quota', "\n";
echo 'You have ',
$quota['count'],
' of ',
$quota['quota']['count'],
' messages and use ';
echo $quota['size'], ' of ', $quota['quota']['size'], ' octets';
Si desea especificar su propia cuota en lugar de usar la indicada en el archivo
maildirsize puede hacerlo con setQuota():
// message count and octet size supported, order does matter
$quota = $mail->setQuota(array('size' => 10000, 'count' => 100));
Para añadir sus propias comprobaciones de cuota, use letras individuales como claves,
y se conservarán (aunque, obviamente, no se comprobarán). También es posible extender
Zend_Mail_Storage_Writable_Maildir para definir su propia
cuota solo si falta el archivo maildirsize (lo que puede ocurrir en Maildir++):
class Example_Mail_Storage_Maildir extends Zend_Mail_Storage_Writable_Maildir {
// getQuota is called with $fromStorage = true by quota checks
public function getQuota($fromStorage = false) {
try {
return parent::getQuota($fromStorage);
} catch (Zend_Mail_Storage_Exception $e) {
if (!$fromStorage) {
// unknown error:
throw $e;
}
// maildirsize file must be missing
list($count, $size) = get_quota_from_somewhere_else();
return array('count' => $count, 'size' => $size);
}
}
}