Author: Alexandru Stanoi Date: 2007-01-11 15:38:28 +0100 (Thu, 11 Jan 2007) New Revision: 4491
Log: - Added fetchParts() to ezcMail to return the mail parts of a mail (feature request #8303). - Added walkParts() to ezcMail and the class ezcMailPartWalkContext which can be used to walk through all the parts in a mail and execute a callback function on each part (for example save mail parts to disk or a database). Added: trunk/Mail/src/structs/walk_context.php trunk/Mail/tests/parser/walk_context_test.php Modified: trunk/Mail/ChangeLog trunk/Mail/src/mail.php trunk/Mail/src/mail_autoload.php trunk/Mail/tests/parser/parser_test.php trunk/Mail/tests/suite.php Modified: trunk/Mail/ChangeLog =================================================================== --- trunk/Mail/ChangeLog 2007-01-11 14:01:30 UTC (rev 4490) +++ trunk/Mail/ChangeLog 2007-01-11 14:38:28 UTC (rev 4491) @@ -3,6 +3,11 @@ - ezcMailTransportException inherits from ezcMailException, and not directly from ezcBaseException. +- Added fetchParts() to ezcMail to return the mail parts of a mail (feature + request #8303). +- Added walkParts() to ezcMail and the class ezcMailPartWalkContext which can + be used to walk through all the parts in a mail and execute a callback + function on each part (for example save mail parts to disk or a database). 1.2.1 - [RELEASEDATE] Modified: trunk/Mail/src/mail.php =================================================================== --- trunk/Mail/src/mail.php 2007-01-11 14:01:30 UTC (rev 4490) +++ trunk/Mail/src/mail.php 2007-01-11 14:38:28 UTC (rev 4491) @@ -368,5 +368,142 @@ } return parent::generateHeaders(); } + + /** + * Returns an array of mail parts from the current mail. + * + * The array returned contains objects of classes: + * - ezcMailText + * - ezcMailFile + * - ezcMailRfc822Digest + * If the method is called with $includeDigests as true, then the returned + * array will not contain ezcMailRfc822Digest objects, but instead the mail + * parts inside the digests. + * The parameter $filter can be used to restrict the returned mail parts, + * eg. $filter = array( 'ezcMailFile' ) to return only file mail parts. + * + * A typical use for this function is to get a list of attachments from a mail. + * Example: + * <code> + * // $mail is an ezcMail object + * $parts = $mail->fetchParts(); + * // after the above line is executed, $parts will contain an array of mail parts objects, + * // for example one ezcMailText object ($parts[0]) and two ezcMailRfc822Digest objects ($parts[1] and $parts[2]). + * // the ezcMailText object will be used to render the mail text, and the + * // other two objects will be displayed as links ("view attachment") + * + * // when user clicks on one of the two attachments, the parts of that attachment + * // must be retrieved in order to render the attached digest: + * $subparts = $parts[1]->mail->fetchParts(); + * // after the above line is executed, $subparts will contain an array of mail parts objects, + * // for example one ezcMailText object and one ezcMailFile object + * </code> + * + * @param array(string) $filter + * @param bool $includeDigests + * @return array(ezcMailPart) + */ + public function fetchParts( $filter = null, $includeDigests = false ) + { + $context = new ezcMailPartWalkContext( array( __CLASS__, 'collectPart' ) ); + $context->includeDigests = $includeDigests; + $context->filter = $filter; + $context->level = 0; + $this->walkParts( $context, $this ); + return $context->getParts(); + } + + /** + * Walks recursively through the mail parts in the specified mail object. + * + * $context is an object of class ezcMailPartWalkContext, which must contain + * a valid callback function name to be applied to all mail parts. You can use + * the collectPart() method, or create your own callback function which can + * for example save the mail parts to disk or to a database. + * + * @see ezcMailPartWalkContext for the properties you can set to the walk context. + * + * Example: + * <code> + * class App + * { + * public static function saveMailPart( $context, $mailPart ) + * { + * // code to save the $mailPart object to disk + * } + * } + * + * // use the saveMailPart() function as a callback in walkParts() + * // where $mail is an ezcMail object. + * $context = new ezcMailPartWalkContext( array( 'App', 'saveMailPart' ) ); + * $context->includeDigests = true; // if you want to go through the digests in the mail + * $mail->walkParts( $context, $mail ); + * </code> + * + * @param ezcMailPartWalkContext $context + * @param ezcMailPart $mail + */ + public function walkParts( ezcMailPartWalkContext $context, ezcMailPart $mail ) + { + $className = get_class( $mail ); + $context->level++; + switch ( $className ) + { + case 'ezcMail': + case 'ezcMailComposer': + $this->walkParts( $context, $mail->body ); + break; + + case 'ezcMailMultipartMixed': + case 'ezcMailMultipartAlternative': + case 'ezcMailMultipartDigest': + foreach ( $mail->getParts() as $part ) + { + $this->walkParts( $context, $part ); + } + break; + + case 'ezcMailMultipartRelated': + $this->walkParts( $context, $mail->getMainPart() ); + foreach ( $mail->getRelatedParts() as $part ) + { + $this->walkParts( $context, $part ); + } + break; + + case 'ezcMailRfc822Digest': + if ( $context->includeDigests ) + { + $this->walkParts( $context, $mail->mail ); + } + elseif ( empty( $context->filter ) || in_array( $className, $context->filter ) ) + { + call_user_func( $context->callbackFunction, $context, $mail ); + } + break; + + case 'ezcMailText': + case 'ezcMailFile': + if ( empty( $context->filter ) || in_array( $className, $context->filter ) ) + { + call_user_func( $context->callbackFunction, $context, $mail ); + } + break; + } + $context->level--; + } + + /** + * Saves $mail in the $context object. + * + * This function is used as a callback in the fetchParts() method. + * + * @param ezcMailPartWalkContext $context + * @param ezcMailPart $mail + */ + protected static function collectPart( ezcMailPartWalkContext $context, ezcMailPart $mail ) + { + $context->appendPart( $mail ); + } } ?> Modified: trunk/Mail/src/mail_autoload.php =================================================================== --- trunk/Mail/src/mail_autoload.php 2007-01-11 14:01:30 UTC (rev 4490) +++ trunk/Mail/src/mail_autoload.php 2007-01-11 14:38:28 UTC (rev 4491) @@ -13,6 +13,7 @@ return array( 'ezcMailAddress' => 'Mail/structs/mail_address.php', 'ezcMailContentDispositionHeader'=> 'Mail/structs/content_disposition_header.php', + 'ezcMailPartWalkContext' => 'Mail/structs/walk_context.php', 'ezcMailPart' => 'Mail/interfaces/part.php', 'ezcMailTransport' => 'Mail/interfaces/transport.php', 'ezcMail' => 'Mail/mail.php', Added: trunk/Mail/src/structs/walk_context.php =================================================================== --- trunk/Mail/src/structs/walk_context.php 2007-01-11 14:01:30 UTC (rev 4490) +++ trunk/Mail/src/structs/walk_context.php 2007-01-11 14:38:28 UTC (rev 4491) @@ -0,0 +1,188 @@ +<?php +/** + * @copyright Copyright (C) 2005, 2006 eZ systems as. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + * @version //autogentag// + * @filesource + * @package Mail + */ + +/** + * Use this class to create a context to be passed to the walkParts() method from ezcMail. + * + * Example: + * <code> + * class App + * { + * public static function saveMailPart( $context, $mailPart ) + * { + * // code to save the $mailPart object to disk + * } + * } + * + * // use the saveMailPart() function as a callback in walkParts() + * // where $mail is an ezcMail object. + * $context = new ezcMailPartWalkContext( array( 'App', 'saveMailPart' ) ); + * $context->includeDigests = true; // if you want to go through the digests in the mail + * $mail->walkParts( $context, $mail ); + * </code> + * + * @property array(string) $filter + * Used to restrict processing only to the specified mail part names. + * If empty or null, then ezcMailText, ezcMailFile and ezcMailRfc822Digest + * parts are processed. Usage e.g.: array( 'ezcMailFile' ) + * @property callback $callbackFunction + * Name of a function or array( 'class_name', 'function_name' ) + * @property bool $includeDigests + * If true then then ezcMailRfc822Digest parts are not processed by + * the callback function, instead the mail parts inside the digests will + * be available for processing. + * @property int $level + * The current level in the mail part walk (0 = first level). + * + * @package Mail + * @version //autogentag// + */ +class ezcMailPartWalkContext +{ + /** + * An array of mail parts (retrieved recursively from a mail object). + * + * @var array(ezcMailPart) + */ + protected $parts = array(); + + /** + * Holds the properties of this class. + * + * @var array(string=>mixed) + */ + private $properties = array(); + + /** + * Constructs a new ezcMailPartWalkContext object. + * + * The parameter $callbackFunction must be a function name as string or as + * array( 'class_name', 'function_name' ). + * + * @param callback $callbackFunction + */ + public function __construct( $callbackFunction ) + { + $this->callbackFunction = $callbackFunction; + $this->level = 0; + $this->filter = array(); + $this->includeDigests = false; + } + + /** + * Sets the property $name to $value. + * + * @throws ezcBasePropertyNotFoundException + * if the property $name does not exist + * @throws ezcBaseValueException + * if $value is not appropiate for property $name + * @param string $name + * @param mixed $value + * @ignore + */ + public function __set( $name, $value ) + { + switch ( $name ) + { + case 'level': + if ( !is_numeric( $value) || $value < 0 ) + { + throw new ezcBaseValueException( $name, $value, 'int >= 0' ); + } + $this->properties[$name] = (int) $value; + break; + + case 'includeDigests': + if ( !is_bool( $value ) ) + { + throw new ezcBaseValueException( $name, $value, 'bool' ); + } + $this->properties[$name] = (bool) $value; + break; + + case 'filter': + $this->properties[$name] = $value; + break; + + case 'callbackFunction': + $this->properties[$name] = $value; + break; + + default: + throw new ezcBasePropertyNotFoundException( $name ); + } + } + + /** + * Returns the value of the property $name. + * + * @throws ezcBasePropertyNotFoundException + * if the property $name does not exist + * @param string $name + * @return mixed + * @ignore + */ + public function __get( $name ) + { + switch ( $name ) + { + case 'level': + case 'filter': + case 'callbackFunction': + case 'includeDigests': + return $this->properties[$name]; + + default: + throw new ezcBasePropertyNotFoundException( $name ); + } + } + + /** + * Returns true if the property $name is set, otherwise false. + * + * @param string $name + * @return bool + * @ignore + */ + public function __isset( $name ) + { + switch ( $name ) + { + case 'level': + case 'filter': + case 'callbackFunction': + case 'includeDigests': + return isset( $this->properties[$name] ); + + default: + return false; + } + } + + /** + * Appends a part to the list of mail parts. + * + * @param ezcMailPart $part + */ + public function appendPart( ezcMailPart $part ) + { + $this->parts[] = $part; + } + + /** + * Returns the mail parts. + * + * @return array(ezcMailPart) + */ + public function getParts() + { + return $this->parts; + } +} +?> Property changes on: trunk/Mail/src/structs/walk_context.php ___________________________________________________________________ Name: svn:eol-style + native Modified: trunk/Mail/tests/parser/parser_test.php =================================================================== --- trunk/Mail/tests/parser/parser_test.php 2007-01-11 14:01:30 UTC (rev 4490) +++ trunk/Mail/tests/parser/parser_test.php 2007-01-11 14:38:28 UTC (rev 4491) @@ -920,5 +920,163 @@ $mail = $mail[0]; $this->assertEquals( '[EMAIL PROTECTED]', $mail->returnPath->email ); } + + public function testGetPartsNoFilterNoDigest() + { + $parser = new ezcMailParser(); + $set = new SingleFileSet( 'various/test-html-text-and-attachment' ); + $mail = $parser->parseMail( $set ); + $mail = $mail[0]; + $parts = $mail->fetchParts( null, false ); + $expected = array( 'ezcMailText', + 'ezcMailText', + 'ezcMailFile', + 'ezcMailFile' + ); + $this->assertEquals( 4, count( $parts ) ); + for ( $i = 0; $i < count( $parts ); $i++ ) + { + $this->assertEquals( $expected[$i], get_class( $parts[$i] ) ); + } + } + + public function testGetPartsFilterNoDigest() + { + $parser = new ezcMailParser(); + $set = new SingleFileSet( 'various/test-html-text-and-attachment' ); + $mail = $parser->parseMail( $set ); + $mail = $mail[0]; + $parts = $mail->fetchParts( array( 'ezcMailText' ), false ); + $expected = array( 'ezcMailText', + 'ezcMailText' + ); + $this->assertEquals( 2, count( $parts ) ); + for ( $i = 0; $i < count( $parts ); $i++ ) + { + $this->assertEquals( $expected[$i], get_class( $parts[$i] ) ); + } + } + + public function testGetPartsFilterNoDigestIncludeDigests() + { + $parser = new ezcMailParser(); + $set = new SingleFileSet( 'various/test-html-text-and-attachment' ); + $mail = $parser->parseMail( $set ); + $mail = $mail[0]; + $parts = $mail->fetchParts( array( 'ezcMailText' ), true ); + $expected = array( 'ezcMailText', + 'ezcMailText' + ); + $this->assertEquals( 2, count( $parts ) ); + for ( $i = 0; $i < count( $parts ); $i++ ) + { + $this->assertEquals( $expected[$i], get_class( $parts[$i] ) ); + } + } + + public function testGetPartsNoFilter() + { + $parser = new ezcMailParser(); + $set = new SingleFileSet( 'pine/three_message_digest.mail' ); + $mail = $parser->parseMail( $set ); + $mail = $mail[0]; + $parts = $mail->fetchParts( null, false ); + $expected = array( 'ezcMailText', + 'ezcMailRfc822Digest', + 'ezcMailRfc822Digest', + 'ezcMailRfc822Digest', + 'ezcMailFile' + ); + $this->assertEquals( 5, count( $parts ) ); + for ( $i = 0; $i < count( $parts ); $i++ ) + { + $this->assertEquals( $expected[$i], get_class( $parts[$i] ) ); + } + } + + public function testGetPartsNoFilterIncludeDigests() + { + $parser = new ezcMailParser(); + $set = new SingleFileSet( 'pine/three_message_digest.mail' ); + $mail = $parser->parseMail( $set ); + $mail = $mail[0]; + $parts = $mail->fetchParts( null, true ); + $expected = array( 'ezcMailText', + 'ezcMailText', + 'ezcMailText', + 'ezcMailFile', + 'ezcMailText', + 'ezcMailFile' + ); + $this->assertEquals( 6, count( $parts ) ); + for ( $i = 0; $i < count( $parts ); $i++ ) + { + $this->assertEquals( $expected[$i], get_class( $parts[$i] ) ); + } + } + + public function testGetPartsFilter() + { + $parser = new ezcMailParser(); + $set = new SingleFileSet( 'pine/three_message_digest.mail' ); + $mail = $parser->parseMail( $set ); + $mail = $mail[0]; + $parts = $mail->fetchParts( array( 'ezcMailFile' ), false ); + $expected = array( 'ezcMailFile' + ); + $this->assertEquals( 1, count( $parts ) ); + for ( $i = 0; $i < count( $parts ); $i++ ) + { + $this->assertEquals( $expected[$i], get_class( $parts[$i] ) ); + } + } + + public function testGetPartsFilterIncludeDigests() + { + $parser = new ezcMailParser(); + $set = new SingleFileSet( 'pine/three_message_digest.mail' ); + $mail = $parser->parseMail( $set ); + $mail = $mail[0]; + $parts = $mail->fetchParts( array( 'ezcMailFile' ), true ); + $expected = array( 'ezcMailFile', + 'ezcMailFile' + ); + $this->assertEquals( 2, count( $parts ) ); + for ( $i = 0; $i < count( $parts ); $i++ ) + { + $this->assertEquals( $expected[$i], get_class( $parts[$i] ) ); + } + } + + public function testGetPartsFilterOnlyDigestIncludeDigests() + { + $parser = new ezcMailParser(); + $set = new SingleFileSet( 'pine/three_message_digest.mail' ); + $mail = $parser->parseMail( $set ); + $mail = $mail[0]; + $parts = $mail->fetchParts( array( 'ezcMailRfc822Digest' ), true ); + $this->assertEquals( 0, count( $parts ) ); + } + + public function testGetPartsDigestInDigestNoFilterIncludeDigests() + { + $parser = new ezcMailParser(); + $set = new SingleFileSet( 'various/test-digest-in-digest' ); + $mail = $parser->parseMail( $set ); + $mail = $mail[0]; + $parts = $mail->fetchParts( null, true ); + $expected = array( 'ezcMailText', + 'ezcMailText', + 'ezcMailText', + 'ezcMailFile', + 'ezcMailFile', + 'ezcMailFile' + ); + $this->assertEquals( 6, count( $parts ) ); + for ( $i = 0; $i < count( $parts ); $i++ ) + { + $this->assertEquals( $expected[$i], get_class( $parts[$i] ) ); + } + } } ?> Added: trunk/Mail/tests/parser/walk_context_test.php =================================================================== --- trunk/Mail/tests/parser/walk_context_test.php 2007-01-11 14:01:30 UTC (rev 4490) +++ trunk/Mail/tests/parser/walk_context_test.php 2007-01-11 14:38:28 UTC (rev 4491) @@ -0,0 +1,89 @@ +<?php +/** + * @copyright Copyright (C) 2005, 2006 eZ systems as. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + * @version //autogentag// + * @filesource + * @package Mail + * @subpackage Tests + */ + +/** + * @package Mail + * @subpackage Tests + */ +class ezcMailPartWalkContextTest extends ezcTestCase +{ + public function testProperties() + { + $context = new ezcMailPartWalkContext( array( 'WalkContextTestApp', 'saveMailPart' ) ); + $context->includeDigests = true; + $context->level = 3; + $context->filter = array( 'ezcMailFile' ); + $this->assertEquals( true, $context->includeDigests ); + $this->assertEquals( 3, $context->level ); + $this->assertEquals( array( 'ezcMailFile' ), $context->filter ); + } + + public function testPropertiesInvalid() + { + $context = new ezcMailPartWalkContext( array( 'WalkContextTestApp', 'saveMailPart' ) ); + try + { + $context->no_such_property = true; + } + catch ( ezcBasePropertyNotFoundException $e ) + { + } + + try + { + $context->level = -1; + } + catch ( ezcBaseValueException $e ) + { + } + + try + { + $context->includeDigests = "yes"; + } + catch ( ezcBaseValueException $e ) + { + } + + try + { + $test = $context->no_such_property; + } + catch ( ezcBasePropertyNotFoundException $e ) + { + } + } + + public function testIsSet() + { + $context = new ezcMailPartWalkContext( array( 'WalkContextTestApp', 'saveMailPart' ) ); + $this->assertEquals( true, isset( $context->includeDigests ) ); + $this->assertEquals( true, isset( $context->filter ) ); + $this->assertEquals( true, isset( $context->level ) ); + $this->assertEquals( true, isset( $context->callbackFunction ) ); + $this->assertEquals( false, isset( $context->no_such_property ) ); + } + + public static function suite() + { + return new PHPUnit_Framework_TestSuite( "ezcMailPartWalkContextTest" ); + } +} + +/** + * Test class. + */ +class WalkContextTestApp +{ + public static function saveMailPart() + { + } +} +?> Property changes on: trunk/Mail/tests/parser/walk_context_test.php ___________________________________________________________________ Name: svn:eol-style + native Modified: trunk/Mail/tests/suite.php =================================================================== --- trunk/Mail/tests/suite.php 2007-01-11 14:01:30 UTC (rev 4490) +++ trunk/Mail/tests/suite.php 2007-01-11 14:38:28 UTC (rev 4491) @@ -31,6 +31,7 @@ require_once( "tutorial_examples.php" ); require_once( "parser/parser_test.php" ); require_once( "parser/headers_holder_test.php" ); +require_once( "parser/walk_context_test.php" ); require_once( "parser/parts/multipart_mixed_test.php" ); require_once( "parser/rfc2231_implementation_test.php" ); require_once( "header_folder_test.php" ); @@ -65,6 +66,7 @@ $this->addTest( ezcMailTransportVariableTest::suite() ); $this->addTest( ezcMailTutorialExamples::suite() ); $this->addTest( ezcMailParserTest::suite() ); + $this->addTest( ezcMailPartWalkContextTest::suite() ); $this->addTest( ezcMailHeadersHolderTest::suite() ); $this->addTest( ezcMailMultipartMixedParserTest::suite() ); $this->addTest( ezcMailRfc2231ImplementationTest::suite() ); -- svn-components mailing list svn-components@lists.ez.no http://lists.ez.no/mailman/listinfo/svn-components