'Mail system', 'description' => 'Performs tests on the pluggable mailing framework.', 'group' => 'System', ); } function setUp() { parent::setUp(array('simpletest')); // Set MailTestCase (i.e. this class) as the SMTP library variable_set('mail_system', array('default-system' => 'MailTestCase')); } /** * Assert that the pluggable mail system is functional. */ function testPluggableFramework() { global $language; // Use MailTestCase for sending a message. $message = drupal_mail('simpletest', 'mail_test', 'testing@example.com', $language); // Assert whether the message was sent through the send function. $this->assertEqual(self::$sent_message['to'], 'testing@example.com', 'Pluggable mail system is extendable.'); } /** * Test that message sending may be canceled. * * @see simpletest_mail_alter() */ function testCancelMessage() { global $language; // Reset the class variable holding a copy of the last sent message. self::$sent_message = NULL; // Send a test message that simpletest_mail_alter should cancel. $message = drupal_mail('simpletest', 'cancel_test', 'cancel@example.com', $language); // Assert that the message was not actually sent. $this->assertNull(self::$sent_message, 'Message was canceled.'); } /** * Checks for the site name in an auto-generated From: header. */ function testFromHeader() { global $language; $default_from = variable_get('site_mail', ini_get('sendmail_from')); $site_name = variable_get('site_name', 'Drupal'); // Reset the class variable holding a copy of the last sent message. self::$sent_message = NULL; // Send an e-mail with a sender address specified. $from_email = 'someone_else@example.com'; $message = drupal_mail('simpletest', 'from_test', 'from_test@example.com', $language, array(), $from_email); // Test that the from e-mail is just the e-mail and not the site name and // default sender e-mail. $this->assertEqual($from_email, self::$sent_message['headers']['From']); // Check default behavior is only email in FROM header. self::$sent_message = NULL; // Send an e-mail and check that the From-header contains only default mail address. variable_del('mail_display_name_site_name'); $message = drupal_mail('simpletest', 'from_test', 'from_test@example.com', $language); $this->assertEqual($default_from, self::$sent_message['headers']['From']); self::$sent_message = NULL; // Send an e-mail and check that the From-header contains the site name. variable_set('mail_display_name_site_name', TRUE); $message = drupal_mail('simpletest', 'from_test', 'from_test@example.com', $language); $this->assertEqual($site_name . ' <' . $default_from . '>', self::$sent_message['headers']['From']); } /** * Checks for the site name in an auto-generated From: header. */ function testFromHeaderRfc2822Compliant() { global $language; $default_from = variable_get('site_mail', ini_get('sendmail_from')); // Enable adding a site name to From. variable_set('mail_display_name_site_name', TRUE); $site_names = array( // Simple ASCII characters. 'Test site' => 'Test site', // ASCII with html entity. 'Test & site' => 'Test & site', // Non-ASCII characters. 'Tést site' => '=?UTF-8?B?VMOpc3Qgc2l0ZQ==?=', // Non-ASCII with special characters. 'Tést; site' => '=?UTF-8?B?VMOpc3Q7IHNpdGU=?=', // Non-ASCII with html entity. 'Tést; site' => '=?UTF-8?B?VMOpc3Q7IHNpdGU=?=', // ASCII with special characters. 'Test; site' => '"Test; site"', // ASCII with special characters as html entity. 'Test < site' => '"Test < site"', // ASCII with special characters and '\'. 'Test; \ "site"' => '"Test; \\\\ \"site\""', // String already RFC-2822 compliant. '"Test; site"' => '"Test; site"', // String already RFC-2822 compliant. '"Test; \\\\ \"site\""' => '"Test; \\\\ \"site\""', ); foreach ($site_names as $original_name => $safe_string) { variable_set('site_name', $original_name); // Reset the class variable holding a copy of the last sent message. self::$sent_message = NULL; // Send an e-mail and check that the From-header contains is RFC-2822 compliant. drupal_mail('simpletest', 'from_test', 'from_test@example.com', $language); $this->assertEqual($safe_string . ' <' . $default_from . '>', self::$sent_message['headers']['From']); } } /** * Concatenate and wrap the e-mail body for plain-text mails. * * @see DefaultMailSystem */ public function format(array $message) { // Join the body array into one string. $message['body'] = implode("\n\n", $message['body']); // Convert any HTML to plain-text. $message['body'] = drupal_html_to_text($message['body']); // Wrap the mail body for sending. $message['body'] = drupal_wrap_mail($message['body']); return $message; } /** * Send function that is called through the mail system. */ public function mail(array $message) { self::$sent_message = $message; } } /** * Unit tests for drupal_html_to_text(). */ class DrupalHtmlToTextTestCase extends DrupalWebTestCase { public static function getInfo() { return array( 'name' => 'HTML to text conversion', 'description' => 'Tests drupal_html_to_text().', 'group' => 'Mail', ); } /** * Converts a string to its PHP source equivalent for display in test messages. * * @param $text * The text string to convert. * * @return * An HTML representation of the text string that, when displayed in a * browser, represents the PHP source code equivalent of $text. */ function stringToHtml($text) { return '"' . str_replace( array("\n", ' '), array('\n', ' '), check_plain($text) ) . '"'; } /** * Helper function for testing drupal_html_to_text(). * * @param $html * The source HTML string to be converted. * @param $text * The expected result of converting $html to text. * @param $message * A text message to display in the assertion message. * @param $allowed_tags * (optional) An array of allowed tags, or NULL to default to the full * set of tags supported by drupal_html_to_text(). */ function assertHtmlToText($html, $text, $message, $allowed_tags = NULL) { preg_match_all('/<([a-z0-6]+)/', drupal_strtolower($html), $matches); $tested_tags = implode(', ', array_unique($matches[1])); $message .= ' (' . $tested_tags . ')'; $result = drupal_html_to_text($html, $allowed_tags); $pass = $this->assertEqual($result, $text, check_plain($message)); $verbose = 'html =
' . $this->stringToHtml($html)
      . '

' . 'result =
' . $this->stringToHtml($result)
      . '

' . 'expected =
' . $this->stringToHtml($text)
      . '
'; $this->verbose($verbose); if (!$pass) { $this->pass("Previous test verbose info:
$verbose"); } } /** * Test all supported tags of drupal_html_to_text(). */ function testTags() { global $base_path, $base_url; $tests = array( // @todo Trailing linefeeds should be trimmed. 'Drupal.org' => "Drupal.org [1]\n\n[1] http://drupal.org\n", // @todo Footer URLs should be absolute. "Homepage" => "Homepage [1]\n\n[1] $base_url/\n", '
Drupal
' => "Drupal\n", // @todo The
tag is currently not supported. '
Drupal
Drupal
' => "DrupalDrupal\n", 'Drupal' => "*Drupal*\n", // @todo There should be a space between the '>' and the text. '
Drupal
' => ">Drupal\n", '
Drupal
Drupal
' => ">Drupal\n>Drupal\n", '
Drupal
Drupal

Drupal' => "Drupal\nDrupal\nDrupal\n", '
Drupal
Drupal

Drupal' => "Drupal\nDrupal\nDrupal\n", // @todo There should be two line breaks before the paragraph. '
Drupal
Drupal

Drupal

Drupal

' => "Drupal\nDrupal\nDrupal\nDrupal\n\n", '
Drupal
' => "Drupal\n", // @todo The
tag is currently not supported. '
Drupal
Drupal
' => "DrupalDrupal\n", 'Drupal' => "/Drupal/\n", '

Drupal

' => "======== DRUPAL ==============================================================\n\n", '

Drupal

Drupal

' => "======== DRUPAL ==============================================================\n\nDrupal\n\n", '

Drupal

' => "-------- DRUPAL --------------------------------------------------------------\n\n", '

Drupal

Drupal

' => "-------- DRUPAL --------------------------------------------------------------\n\nDrupal\n\n", '

Drupal

' => ".... Drupal\n\n", '

Drupal

Drupal

' => ".... Drupal\n\nDrupal\n\n", '

Drupal

' => ".. Drupal\n\n", '

Drupal

Drupal

' => ".. Drupal\n\nDrupal\n\n", '
Drupal
' => "Drupal\n\n", '
Drupal

Drupal

' => "Drupal\n\nDrupal\n\n", '
Drupal
' => "Drupal\n\n", '
Drupal

Drupal

' => "Drupal\n\nDrupal\n\n", '
Drupal
' => "------------------------------------------------------------------------------\nDrupal\n------------------------------------------------------------------------------\n", '
Drupal
' => "------------------------------------------------------------------------------\nDrupal\n------------------------------------------------------------------------------\n", '
Drupal

Drupal

' => "------------------------------------------------------------------------------\nDrupal\n------------------------------------------------------------------------------\nDrupal\n\n", 'Drupal' => "/Drupal/\n", '

Drupal

' => "Drupal\n\n", '

Drupal

Drupal

' => "Drupal\n\nDrupal\n\n", 'Drupal' => "*Drupal*\n", // @todo Tables are currently not supported. '
DrupalDrupal
DrupalDrupal
' => "DrupalDrupalDrupalDrupal\n", '
Drupal

Drupal

' => "Drupal\nDrupal\n\n", // @todo The tag is currently not supported. 'Drupal' => "Drupal\n", '
  • Drupal
' => " * Drupal\n\n", '
  • Drupal Drupal Drupal
' => " * Drupal /Drupal/ Drupal\n\n", // @todo Lines containing nothing but spaces should be trimmed. '
  • Drupal
    1. Drupal
    2. Drupal
' => " * Drupal\n * 1) Drupal\n 2) Drupal\n \n\n", '
  • Drupal
    1. Drupal
  • Drupal
' => " * Drupal\n * 1) Drupal\n \n * Drupal\n\n", '
  • Drupal
  • Drupal
' => " * Drupal\n * Drupal\n\n", '
  • Drupal

Drupal

' => " * Drupal\n\nDrupal\n\n", '
  1. Drupal
' => " 1) Drupal\n\n", '
  1. Drupal
    • Drupal
    • Drupal
' => " 1) Drupal\n 2) * Drupal\n * Drupal\n \n\n", '
  1. Drupal
  2. Drupal
' => " 1) Drupal\n 2) Drupal\n\n", '
    Drupal
' => "Drupal\n\n", '
  1. Drupal

Drupal

' => " 1) Drupal\n\nDrupal\n\n", '
Drupal
' => "Drupal\n\n", '
Drupal
Drupal
' => "Drupal\n Drupal\n\n", '
Drupal
Drupal
Drupal
Drupal
' => "Drupal\n Drupal\nDrupal\n Drupal\n\n", '
Drupal
Drupal

Drupal

' => "Drupal\n Drupal\n\nDrupal\n\n", '
Drupal
Drupal
' => "Drupal\n Drupal\n\n", '
Drupal

Drupal

' => "Drupal\n\nDrupal\n\n", // @todo Again, lines containing only spaces should be trimmed. '
  • Drupal
  • Drupal
    Drupal
    Drupal
    Drupal
  • Drupal
' => " * Drupal\n * Drupal\n Drupal\n Drupal\n Drupal\n \n * Drupal\n\n", // Tests malformed HTML tags. '
Drupal
Drupal' => "Drupal\nDrupal\n", '
Drupal
Drupal' => "------------------------------------------------------------------------------\nDrupal\n------------------------------------------------------------------------------\nDrupal\n", '
  1. Drupal
  2. Drupal
' => " 1) Drupal\n 2) Drupal\n\n", '
  • Drupal Drupal Drupal
' => " * Drupal /Drupal/ Drupal\n\n", '
  • Drupal
  • Drupal' => " * Drupal\n * Drupal\n\n", '
    • Drupal
    • Drupal
    ' => " * Drupal\n * Drupal\n\n", '
      Drupal
    ' => "Drupal\n\n", 'Drupal
  • Drupal' => "Drupal\n * Drupal\n", '
    Drupal
    ' => "Drupal\n\n", '
    Drupal

    Drupal

    ' => "Drupal\n\nDrupal\n\n", '
    Drupal
    ' => "Drupal\n", // Tests some unsupported HTML tags. 'Drupal' => "Drupal\n", // @todo Perhaps the contents of ' => "Drupal\n", ); foreach ($tests as $html => $text) { $this->assertHtmlToText($html, $text, 'Supported tags'); } } /** * Test $allowed_tags argument of drupal_html_to_text(). */ function testDrupalHtmlToTextArgs() { // The second parameter of drupal_html_to_text() overrules the allowed tags. $this->assertHtmlToText( 'Drupal Drupal Drupal', "Drupal *Drupal* Drupal\n", 'Allowed tag found', array('b') ); $this->assertHtmlToText( 'Drupal

    Drupal

    Drupal', "Drupal Drupal Drupal\n", 'Disallowed

    tag not found', array('b') ); $this->assertHtmlToText( 'Drupal

    Drupal

    Drupal', "Drupal Drupal Drupal\n", 'Disallowed

    , , and tags not found', array('a', 'br', 'h1') ); $this->assertHtmlToText( 'Drupal', "Drupal\n", 'Unsupported and tags not found', array('html', 'body') ); } /** * Tests that drupal_wrap_mail() removes trailing whitespace before newlines. */ function testDrupalHtmltoTextRemoveTrailingWhitespace() { $text = "Hi there! \nHerp Derp"; $mail_lines = explode("\n", drupal_wrap_mail($text)); $this->assertNotEqual(" ", substr($mail_lines[0], -1), 'Trailing whitespace removed.'); } /** * Tests drupal_wrap_mail() retains whitespace from Usenet style signatures. * * RFC 3676 says, "This is a special case; an (optionally quoted or quoted and * stuffed) line consisting of DASH DASH SP is neither fixed nor flowed." */ function testDrupalHtmltoTextUsenetSignature() { $text = "Hi there!\n-- \nHerp Derp"; $mail_lines = explode("\n", drupal_wrap_mail($text)); $this->assertEqual("-- ", $mail_lines[1], 'Trailing whitespace not removed for dash-dash-space signatures.'); $text = "Hi there!\n-- \nHerp Derp"; $mail_lines = explode("\n", drupal_wrap_mail($text)); $this->assertEqual("--", $mail_lines[1], 'Trailing whitespace removed for incorrect dash-dash-space signatures.'); } /** * Test that whitespace is collapsed. */ function testDrupalHtmltoTextCollapsesWhitespace() { $input = "

    Drupal Drupal\n\nDrupal

    Drupal  Drupal\n\nDrupal
    Drupal Drupal\n\nDrupal

    "; // @todo The whitespace should be collapsed. $collapsed = "Drupal Drupal\n\nDrupalDrupal Drupal\n\nDrupalDrupal Drupal\n\nDrupal\n\n"; $this->assertHtmlToText( $input, $collapsed, 'Whitespace is collapsed', array('p') ); } /** * Test that text separated by block-level tags in HTML get separated by * (at least) a newline in the plaintext version. */ function testDrupalHtmlToTextBlockTagToNewline() { $input = '[text]' . '
    [blockquote]
    ' . '
    [br]' . '
    [dl-dt]
    ' . '
    [dt]
    ' . '
    [dd]
    ' . '
    [dd-dl]
    ' . '

    [h1]

    ' . '

    [h2]

    ' . '

    [h3]

    ' . '

    [h4]

    ' . '
    [h5]
    ' . '
    [h6]
    ' . '
    [hr]' . '
    1. [ol-li]
    2. ' . '
    3. [li]
    4. ' . '
    5. [li-ol]
    ' . '

    [p]

    ' . '
    • [ul-li]
    • ' . '
    • [li-ul]
    ' . '[text]'; $output = drupal_html_to_text($input); $pass = $this->assertFalse( preg_match('/\][^\n]*\[/s', $output), 'Block-level HTML tags should force newlines' ); if (!$pass) { $this->verbose($this->stringToHtml($output)); } $output_upper = drupal_strtoupper($output); $upper_input = drupal_strtoupper($input); $upper_output = drupal_html_to_text($upper_input); $pass = $this->assertEqual( $upper_output, $output_upper, 'Tag recognition should be case-insensitive' ); if (!$pass) { $this->verbose( $upper_output . '
    should be equal to
    ' . $output_upper ); } } /** * Test that headers are properly separated from surrounding text. */ function testHeaderSeparation() { $html = 'Drupal

    Drupal

    Drupal'; // @todo There should be more space above the header than below it. $text = "Drupal\n======== DRUPAL ==============================================================\n\nDrupal\n"; $this->assertHtmlToText($html, $text, 'Text before and after

    tag'); $html = '

    Drupal

    Drupal

    Drupal'; // @todo There should be more space above the header than below it. $text = "Drupal\n\n======== DRUPAL ==============================================================\n\nDrupal\n"; $this->assertHtmlToText($html, $text, 'Paragraph before and text after

    tag'); $html = 'Drupal

    Drupal

    Drupal

    '; // @todo There should be more space above the header than below it. $text = "Drupal\n======== DRUPAL ==============================================================\n\nDrupal\n\n"; $this->assertHtmlToText($html, $text, 'Text before and paragraph after

    tag'); $html = '

    Drupal

    Drupal

    Drupal

    '; $text = "Drupal\n\n======== DRUPAL ==============================================================\n\nDrupal\n\n"; $this->assertHtmlToText($html, $text, 'Paragraph before and after

    tag'); } /** * Test that footnote references are properly generated. */ function testFootnoteReferences() { global $base_path, $base_url; $source = 'Host and path' . '
    Host, no path' . '
    Path, no host' . '
    Relative path'; // @todo Footnote URLs should be absolute. $tt = "Host and path [1]" . "\nHost, no path [2]" // @todo The following two references should be combined. . "\nPath, no host [3]" . "\nRelative path [4]" . "\n" . "\n[1] http://www.example.com/node/1" . "\n[2] http://www.example.com" // @todo The following two references should be combined. . "\n[3] $base_url/node/1" . "\n[4] node/1\n"; $this->assertHtmlToText($source, $tt, 'Footnotes'); } /** * Test that combinations of paragraph breaks, line breaks, linefeeds, * and spaces are properly handled. */ function testDrupalHtmlToTextParagraphs() { $tests = array(); $tests[] = array( 'html' => "

    line 1
    \nline 2
    line 3\n
    line 4

    paragraph

    ", // @todo Trailing line breaks should be trimmed. 'text' => "line 1\nline 2\nline 3\nline 4\n\nparagraph\n\n", ); $tests[] = array( 'html' => "

    line 1
    line 2

    line 4
    line 5

    0

    ", // @todo Trailing line breaks should be trimmed. 'text' => "line 1\nline 2\n\nline 4\nline 5\n\n0\n\n", ); foreach ($tests as $test) { $this->assertHtmlToText($test['html'], $test['text'], 'Paragraph breaks'); } } /** * Tests that drupal_html_to_text() wraps before 1000 characters. * * RFC 3676 says, "The Text/Plain media type is the lowest common * denominator of Internet email, with lines of no more than 998 characters." * * RFC 2046 says, "SMTP [RFC-821] allows a maximum of 998 octets before the * next CRLF sequence." * * RFC 821 says, "The maximum total length of a text line including the * is 1000 characters." */ function testVeryLongLineWrap() { $input = 'Drupal

    ' . str_repeat('x', 2100) . '


    Drupal'; $output = drupal_html_to_text($input); // This awkward construct comes from includes/mail.inc lines 8-13. $eol = variable_get('mail_line_endings', MAIL_LINE_ENDINGS); // We must use strlen() rather than drupal_strlen() in order to count // octets rather than characters. $line_length_limit = 1000 - drupal_strlen($eol); $maximum_line_length = 0; foreach (explode($eol, $output) as $line) { // We must use strlen() rather than drupal_strlen() in order to count // octets rather than characters. $maximum_line_length = max($maximum_line_length, strlen($line . $eol)); } $verbose = 'Maximum line length found was ' . $maximum_line_length . ' octets.'; $this->assertTrue($maximum_line_length <= 1000, $verbose); } }