View Source

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{zone-template-instance:ZFDEV:Zend Proposal Zone Template}

{zone-data:component-name}
Zend_Mail_Read
{zone-data}

{zone-data:proposer-list}
[Nico Edtinger|mailto:alpha@rrs.at], [Telekom Austria|http://wai.telekom.at/]
{zone-data}

{zone-data:revision}
1.0 - 26 June 2006: Merged previous proposals ([Zend_Mime_Decode|http://framework.zend.com/svn/framework/trunk/documentation/proposals/pending/Zend_Mime/Zend_Mime_Decode-proposal.txt], [Zend_Mail_Read|http://framework.zend.com/svn/framework/trunk/documentation/proposals/pending/Zend_Mail/Zend_Mail_Read-proposal.txt], [Zend_Mail_List|http://framework.zend.com/svn/framework/trunk/documentation/proposals/pending/Zend_Mime/Zend_Mime_Decode-proposal.txt]).
{zone-data}

{zone-data:overview}
Mail is one of the most important services on the internet. In addition to
sending mails it's also important for many websites to read and receive mail
messages, be it simple webmail interfaces, archives of mailing lists or
cron jobs that need to read incoming mails.

There are many existing mail libs, even in PHP, but they often don't fit
well. Some of them are to strict or to sloppy, others are made for a specific
application. And then there is some code written in C, that's good, but
not part of the default PHP distribution.

The goal is to offer classes for Mbox, Maildir, POP3 and IMAP and reuse
the existing code of the mail sending classes. Also links between these two
are possible, like reply to a message or sending to an IMAP folder.

Note: Some methods or parameters needed for IMAP may be missing in
this proposal (folder, flags, save message)
{zone-data}

{zone-data:references}
Inspirations: (the good, the bad and the ugly - in no particular order)

* PECL: [mailparse|http://pecl.php.net/mailparse], [POP3|http://pecl.php.net/pop3]
* PEAR: [Mail_IMAP|http://pear.php.net/Mail_IMAP], [Mail_Mbox|http://pear.php.net/Mail_mbox]
* eZ Components: [ezcMailPop3Transport|http://svn.ez.no/filedetails.php?repname=ezcomponents&path=%2Ftrunk%2FMail%2Fsrc%2Ftransports%2Fpop3%2Fpop3_transport.php]
* PHP: [c-client lib|http://www.washington.edu/imap/], [imap_*|http://php.net/imap]
{zone-data}

{zone-data:requirements}
* Create an easy and common API to fetch messages from mail storages.
* Make it possible to access additonal functionality of mail storages without coding for one specific mail storage (via capabilities).
* Make the transport class reusable and add not additional logic to the protocol.
* Split MIME decoding in its own class without changing existing mail and mime classes.
* Map mail reading functions to PHP with Zend_Mail_List (implementing all the funky interfaces for accessing the mail storage as array and iteration)
* obey the standards
* be lax with received data without violating the standards
{zone-data}


{zone-data:dependencies}
* Zend_Exception
* Zend_Mime - for decoding and as base for message class
{zone-data}

{zone-data:operation}
|| Zend_Mime_Decode ||
The class has no internal logic. It's used to collect all decoding functions as static methods
for mime and mail classes. Normally this methods would be used by other framework components.

|| Zend_Mail_*(Pop3,Imap,Mbox,Maildir) ||
The classes are divided in:
* transport classes
* mail classes
* message classes

All mail messages are stored in a message storage. If the
storage is external (not read from a file resource) a transport
class is used to translate the used protocol in PHP structures.

Based on this classes are the mail classes, or if an internal
storage is used the mail classes can read directly from the
storage. These classes extend an abstract mail class defining
the common interface.

Each message is returned as message class, which can be specialized
to support mail storage specific functions like late fetching of
the whole message or parts of a message.

If the message is a multipart message it can return each part, which
could itself be multipart.

!zend_mail_read.png!

|| Zend_Mail_List ||
Zend_Mail_List translates the interfaces, which the PHP engine needs, to the
mail reading APIs. ArrayAccess and Countable are implemented to access
a specific message and count all messages. Iterator is used for iterating
over all messages.
{zone-data}

{zone-data:class-list}
* Zend_Mime_Decode
* Zend_Mail_Message
* Zend_Mail_Abstract
* Zend_Mail_Mbox
* Zend_Mail_POP3
* Zend_Mail_Transport_POP3
* Zend_Mail_Maildir
* Zend_Mail_Imap
* Zend_Mail_Transport_Imap
* Zend_Mail_List
{zone-data}

{zone-data:use-cases}
|| Zend_Mime_Decode ||

{code}
<?php

Zend::loadClass('Zend_Mime_Decode');

function getMimeStruct($data, $boundary = null)
{
if(!$boundary) {
$struct = array(Zend_Mime_Decode::splitMessageStruct($data));
} else {
$struct = Zend_Mime_Decode::splitMessageStruct($data, $boundary);
}

foreach($struct as $key => $value) {
$struct[$key]['child'] = null;
if(!isset($value['header']['content-type'])) {
continue;
}
$boundary = Zend_Mime_Decode::splitContentType($value['header']['content-type'], 'boundary');
if(!$boundary) {
continue;
}
$struct[$key]['child'] = getMimeStruct($value['body'], $boundary);
}

return $struct;
}

Zend::dump(getMimeStruct($aComplexMimeDocument));

?>
{code}

|| Zend_Mail_List ||

{code}
<?php

Zend::loadClass('Zend_Mail_Mbox');
Zend::loadClass('Zend_Mail_List');

$mail = new Zend_Mail_Mbox(array('filename' => 'foo.mbox'));
$list = new Zend_Mail_List($mail);

echo count($list), " mails in your mbox file\n";
foreach($list as $message) {
echo $message->subject, "\n";
}

?>
{code}

|| Zend_Mail_Mbox ||

{code}
<?php

Zend::loadClass('Zend_Mail_Mbox');

$mail = new Zend_Mail_Mbox(array('filename' => 'foo.mbox'));
echo $mail->countMessages(), " mails in your mbox file\n";
echo "== First message ==\n";
$message = $mail->getMessage(1);
echo 'from ', $message->from, ' to ', $message->to, "\n";
echo 'with subject "', $message->subject, "\"\n---\n";
echo $message->getContent();
?>
{code}

|| Zend_Mail_* full functional demo ||
{code}
<?php
class Example3 {
private $queryString, $type, $param, $messageNum, $mail, $noRun = false;

function __construct() {
$this->loadClasses();
$this->initVars();
switch($this->type) {
case 'mbox':
$this->mail = new Zend_Mail_Mbox(array('filename' => $this->param));
break;
case 'pop3':
if(!isset($_SERVER['PHP_AUTH_USER'])) {
$this->needAuth();
} else {
$this->mail = new Zend_Mail_Pop3(array(
'host' => $this->param, 'user' => $_SERVER['PHP_AUTH_USER'], 'password' => $_SERVER['PHP_AUTH_PW']));
}
break;
default:
$this->mail = null;
}
}

function loadClasses() {
include_once 'init.inc';
Zend::loadClass('Zend_Mail_Mbox');
Zend::loadClass('Zend_Mail_Pop3');
Zend::loadClass('Zend_Mail_List');
}

function initVars() {
$this->type = isset($_GET['type']) ? $_GET['type'] : null;
$this->param = isset($_GET['param']) ? $_GET['param'] : null;
$this->queryString = "type={$this->type}&param={$this->param}";
$this->messageNum = isset($_GET['message']) && is_numeric($_GET['message']) ? $_GET['message'] : null;
}

function needAuth() {
header('WWW-Authenticate: Basic realm="POP3 credentials"');
header('HTTP/1.0 401 Please enter credentials');
$this->noRun = true;
}

function run() {
if($this->noRun) {
return;
}
$message = null;
try {
if($this->messageNum) {
$message = $this->mail->getMessage($this->messageNum);
}
} catch(Zend_Mail_Exception $e) {}
if(!$this->mail) {
$this->showChooseType();
} else if($message) {
$this->showMessage($message);
} else {
$this->showList();
}
}

function showHeader($title) {
echo "<html><head><title>{$title}</title><style>table {border: 1px solid black; border-collapse: collapse}
td, th {border: 1px solid black; padding: 3px; text-align: left}th {text-align: right; background: #eee}
.message {white-space: pre; font-family: monospace}</style></head><body><h1>{$title}</h1>";
}

function showFooter() {
echo '</body></html>';
}

function showChooseType() {
$this->showHeader('Choose Type');
echo '<form><label>Mbox file</label><input name="param" value="test2.mbox"/>
<input type="hidden" name="type" value="mbox"/><input type="submit"/></form><form><label>Pop3 Host</label>
<input name="param" value="localhost"/><input type="hidden" name="type" value="pop3"/><input type="submit"/></form>';
$this->showFooter();
}

function showMessage($message) {
try {
$from = $message->from;
} catch(Zend_Mail_Exception $e) {
$from = '(unknown)';
}
try {
$to = $message->to;
} catch(Zend_Mail_Exception $e) {
$to = '(unknown)';
}
try {
$subject = $message->subject;
} catch(Zend_Mail_Exception $e) {
$subject = '(unknown)';
}
$content = htmlentities($message->getContent());
$this->showHeader($subject);
echo "<table><tr><th>From:</td><td>$from</td></tr><tr><th>Subject:</td><td>$subject</td></tr><tr><th>To:</td>
<td>$to</td></tr><tr><td colspan='2' class='message'>$content</td></tr></table>
<a href='?{$this->queryString}'>back to list</a>";
if($this->messageNum > 1) {
echo " - <a href=\"?{$this->queryString}&message=", $this->messageNum - 1, '">prev</a>';
}
if($this->messageNum < $this->mail->countMessages()) {
echo " - <a href=\"?{$this->queryString}&message=", $this->messageNum + 1, '">next</a>';
}
$this->showFooter();
}

function showList() {
$this->showHeader('Overview');
echo '<table><tr><td></td><th>From</th><th>To</th><th>Subject</th></tr>';
foreach(new Zend_Mail_List($this->mail) as $num => $message) {
echo "<tr><td><a href='?{$this->queryString}&message=$num'>read</a></td>";
try {
echo "<td>{$message->from}</td><td>{$message->to}</td><td>{$message->subject}</td>";
} catch(Zend_Mail_Exception $e){
echo '<td><em>error</em></td>';
}
echo '</tr>';
}
echo '</table>';
$this->showFooter();
}
}

$example3 = new Example3();
$example3->run();
{code}

{zone-data}

{zone-data:skeletons}
{code}
class Zend_Mime_Decode {
/**
* Splits a message in its MIME parts with the given boundary. The parts are returned as
* array.
*
* @param string mail/mime body
* @param string boundary
* @return array parts
*/
public static function splitMime($body, $boundary);

/**
* Splits a message in headers and body. If a boundary is given the message is also splitted
* in its MIME parts (see splitMessage()). The message/parts is returned as
* array('header' => array($name => $value, ...), 'body' => $body)
*
* @param string message
* @return array message/parts
*/
public static function splitMessageStruct($message, $boundary);

/**
* Splits a message in header and body part. Both are returned as reference.
*
* @param string $message
* @param mixed $headers, output param, out type is array
* @param mixed $body, output param, out type is string
*/
public static function splitMessage($message, &$headers, &$body);

/**
* Uses splitHeaderField() to decode a header named "Content-Type".
*
* @param string content-type
* @param string the wanted part, else an array with all parts is returned
*
* @return string|array wanted part or all parts
*/
public static function splitContentType($type, $wantedPart = null);



/**
* Splits a header field, that's encoded as "firstPart; key = value; key = value"
* The parts are returned as array($name => $value, ...)
* If you want only a specific part you can set $wantedPart and only the value of this
* part is returned.
* The first part has no key and gets its key from $firstName (default: 0)
*
* @param string header field
* @param string the wanted part, else an array with all parts is returned
* @param string key name for the first field
*
* @return string|array wanted part or all parts
*/
public static function splitHeaderField($type, $wantedPart = null, $firstName = 0);

/**
* Decodes a quoted printable encoded string.
*
* @param string encoded string
* @return string decoded string
*/
public static function decodeQuotedPrintable($string);
}
{code}

{code}
class Zend_Mail_Transport_/* any */ {
// depending on the protocol
}
{code}


{code}
class Zend_Mail_Abstract { /* (interface implemented by Zend_Mail_[Mbox|Pop3|...]) */
/**
* class capabilities with default values
*/
protected $_has = array('folder' => false,
'uniqueid' => false,
'delete' => false,
'create' => false,
'top' => false);
/**
* for accessing capabilities: hasFolder, hasUniqueid, hasDelete, hasCreate, hasTop
*
* @param string $var property name
* @return bool supported or not
*/
public function __get($var);

/**
* returns all capabilities at once
*
* @return array list of features as array(featurename => true|false[|null])
*/
public function getCapabilities();

/**
* count messages in current mailbox or folder
*
* @param int $flags filter by flags
* @throws Zend_Mail_Exception
* @return int number of messages
*/
abstract public function countMessages($flags = null);

/**
* get size of a specific message or array with size of all messages in current
* mailbox or folder
*
* @param int $id number of message
* @return int|array size of given message of list with all messages as array(num => size)
*/
abstract public function getSize($id = 0);

/**
* get a message from current mailbox or folder
*
* @param $id int number of message
* @return Zend_Mail_Message
*/
abstract public function getMessage($id);

/**
* fetch only header and $bodyLines lines of message body (default: 0)
*
* @param int $id number of message
* @param int $bodyLines also retrieve this number of body lines
* @return Zend_Mail_Message
*/
abstract public function getHeader($id, $bodyLines = 0);

/**
* Create instance with parameters
*
* @param array $params mail reader specific parameters
* @throws Zend_Mail_Exception
*/
abstract public function __construct($params);

/**
* Destructor calls close() and therefore closes the resource.
*/
public function __destruct();

/**
* Close resource for mail lib. If you need to control, when the resource
* is closed. Otherwise the destructor would call this.
*/
abstract public function close();


/**
* do nothing, but at least check if the transport resource is alive
*/
abstract public function noop();

/**
* remove a message from mail storage
*/
abstract public function removeMessage($id);
}
{code}

{code}
class Zend_Mail_Message {
/**
* for accessing mail headers (case-insensitive)
*
* @param string $name header name
* @throws Zend_Mail_Exception
* @return string|array header line or array of headers if header exists more than once
*/
public function __get($name);

/**
* get one or all headers. If $format is not defined a mail header that occurs more than
* once is returned as array; else as string. With $format it can be forced to be
* an array or a string.
*
* @param string header name
* @param int format
* @return string|array header line or array of headers if header exists more than once
*/
public function getHeader($name = '', $format = 0);

/**
* get whole message
*
* @return string body
*/
public function getContent();

/**
* is message a mime multipart message?
*
* @return bool
*/
public function isMultipart();


/**
* $part can be number of part or a mime type. In the latter case the first found part
* with the given mime type is returned.
*
* @param int|string which part
* @return string part content
*/
public function getPart(mixed $part);

/**
* number of parts
*
* @return int count
*/
public function countParts();
}
{code}

{code}
class Zend_Mail_List implements Countable, ArrayAccess, SeekableIterator {
/**
* init with a existing mail reading class
*
* @param Zend_Mail_Abstract mail reader
*/
public function __construct(Zend_Mail_Abstract $mailReader);

// implemented methods:
// * for Countable: count()
// * for ArrayAccess: offsetExists(int $id), offsetGet(int $id), offsetSet(), offsetUnset()
// offsetSet() is not supported and throws an exception
// * for Iterator: rewind(), current(), key(), next(), valid(), seek()
}
{code}
{zone-data}

{zone-template-instance}]]></ac:plain-text-body></ac:macro>