diff options
author | Dries Buytaert <dries@buytaert.net> | 2009-10-16 03:01:55 +0000 |
---|---|---|
committer | Dries Buytaert <dries@buytaert.net> | 2009-10-16 03:01:55 +0000 |
commit | 13d3072f418835569f37f65b5055e5b3180fad2e (patch) | |
tree | 6964b91e90d3bddbc3d5ce302897c35a248ddf6b | |
parent | b965f7478f34c78b747ad6667738828599e86df7 (diff) | |
download | brdo-13d3072f418835569f37f65b5055e5b3180fad2e.tar.gz brdo-13d3072f418835569f37f65b5055e5b3180fad2e.tar.bz2 |
- Patch #356074 by chx, Damien Tournoud: provide a sequences API.
-rw-r--r-- | includes/mail.inc | 60 | ||||
-rw-r--r-- | includes/module.inc | 61 | ||||
-rw-r--r-- | includes/token.inc | 18 | ||||
-rw-r--r-- | modules/contact/contact.test | 4 | ||||
-rw-r--r-- | modules/simpletest/drupal_web_test_case.php | 2 | ||||
-rw-r--r-- | modules/simpletest/simpletest.test | 6 | ||||
-rw-r--r-- | modules/simpletest/tests/mail.test | 17 | ||||
-rw-r--r-- | modules/system/mail.sending.inc | 34 | ||||
-rw-r--r-- | modules/system/system.api.php | 34 | ||||
-rw-r--r-- | modules/system/system.module | 15 | ||||
-rw-r--r-- | modules/user/user.module | 3 |
11 files changed, 192 insertions, 62 deletions
diff --git a/includes/mail.inc b/includes/mail.inc index f1797b7bb..4b3706188 100644 --- a/includes/mail.inc +++ b/includes/mail.inc @@ -14,7 +14,7 @@ * appropriate places in the template. Processed e-mail templates are * requested from hook_mail() from the module sending the e-mail. Any module * can modify the composed e-mail message array using hook_mail_alter(). - * Finally drupal_mail_sending_system()->mail() sends the e-mail, which can + * Finally drupal_mail_system()->mail() sends the e-mail, which can * be reused if the exact same composed e-mail is to be sent to multiple * recipients. * @@ -78,8 +78,8 @@ * @param $from * Sets From to this value, if given. * @param $send - * Send the message directly, without calling - * drupal_mail_sending_system()->mail() manually. + * Send the message directly, without calling drupal_mail_system()->mail() + * manually. * @return * The $message array structure containing all details of the * message. If already sent ($send = TRUE), then the 'result' element @@ -93,6 +93,8 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N // Bundle up the variables into a structured array for altering. $message = array( 'id' => $module . '_' . $key, + 'module' => $module, + 'key' => $key, 'to' => $to, 'from' => isset($from) ? $from : $default_from, 'language' => $language, @@ -129,12 +131,15 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N // Invoke hook_mail_alter() to allow all modules to alter the resulting e-mail. drupal_alter('mail', $message); - // Concatenate and wrap the e-mail body. - $message['body'] = is_array($message['body']) ? drupal_wrap_mail(implode("\n\n", $message['body'])) : drupal_wrap_mail($message['body']); + // Retrieve the responsible implementation for this message. + $system = drupal_mail_system($module, $key); + + // Format the message body. + $message = $system->format($message); // Optionally send e-mail. if ($send) { - $message['result'] = drupal_mail_sending_system($module, $key)->mail($message); + $message['result'] = $system->mail($message); // Log errors if (!$message['result']) { @@ -149,11 +154,23 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N /** * Returns an object that implements the MailSystemInterface. * - * Allows for one or more custom mail backends to send mail messages + * Allows for one or more custom mail backends to format and send mail messages * composed using drupal_mail(). * + * An implementation needs to implement the following methods: + * - format: Allows to preprocess, format, and postprocess a mail + * message before it is passed to the sending system. By default, all messages + * may contain HTML and are converted to plain-text by the DefaultMailSystem + * implementation. For example, an alternative implementation could override + * the default implementation and additionally sanitize the HTML for usage in + * a MIME-encoded e-mail, but still invoking the DefaultMailSystem + * implementation to generate an alternate plain-text version for sending. + * - mail: Sends a message through a custom mail sending engine. + * By default, all messages are sent via PHP's mail() function by the + * DefaultMailSystem implementation. + * * The selection of a particular implementation is controlled via the variable - * 'mail_sending_system', which is a keyed array. The default implementation + * 'mail_system', which is a keyed array. The default implementation * is the class whose name is the value of 'default-system' key. A more specific * match first to key and then to module will be used in preference to the * default. To specificy a different class for all mail sent by one module, set @@ -195,11 +212,12 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N * A key to identify the e-mail sent. The final e-mail ID for the e-mail * alter hook in drupal_mail() would have been {$module}_{$key}. */ -function drupal_mail_sending_system($module, $key) { +function drupal_mail_system($module, $key) { $instances = &drupal_static(__FUNCTION__, array()); $id = $module . '_' . $key; - $configuration = variable_get('mail_sending_system', array('default-system' => 'DefaultMailSystem')); + + $configuration = variable_get('mail_system', array('default-system' => 'DefaultMailSystem')); // Look for overrides for the default class, starting from the most specific // id, and falling back to the module name. @@ -230,7 +248,18 @@ function drupal_mail_sending_system($module, $key) { */ interface MailSystemInterface { /** - * Send an e-mail message composed by drupal_mail(). + * Format a message composed by drupal_mail() prior sending. + * + * @param $message + * A message array, as described in hook_mail_alter(). + * + * @return + * The formatted $message. + */ + public function format(array $message); + + /** + * Send a message composed by drupal_mail(). * * @param $message * Message array with at least the following elements: @@ -452,9 +481,10 @@ function drupal_html_to_text($string, $allowed_tags = NULL) { } // Process blocks of text. else { - // Convert inline HTML text to plain text. - $value = trim(preg_replace('/\s+/', ' ', decode_entities($value))); - if (strlen($value)) { + // Convert inline HTML text to plain text; not removing line-breaks or + // white-space, since that breaks newlines when sanitizing plain-text. + $value = trim(decode_entities($value)); + if (drupal_strlen($value)) { $chunk = $value; } } @@ -466,7 +496,7 @@ function drupal_html_to_text($string, $allowed_tags = NULL) { $chunk = $casing($chunk); } // Format it and apply the current indentation. - $output .= drupal_wrap_mail($chunk, implode('', $indent)) . "\n"; + $output .= drupal_wrap_mail($chunk, implode('', $indent)); // Remove non-quotation markers from indentation. $indent = array_map('_drupal_html_to_text_clean', $indent); } diff --git a/includes/module.inc b/includes/module.inc index ee5446873..4213469ff 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -361,6 +361,8 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) { if ($reset) { $implementations = array(); cache_set('module_implements', array()); + drupal_static_reset('module_hook_info'); + cache_clear_all('hook_info', 'cache'); return; } @@ -376,18 +378,25 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) { } if (!isset($implementations[$hook])) { + $hook_info = module_hook_info(); $implementations[$hook] = array(); $list = module_list(FALSE, FALSE, $sort); foreach ($list as $module) { - if (module_hook($module, $hook)) { - $implementations[$hook][$module] = $module; + $include_file = FALSE; + if (module_hook($module, $hook) || (isset($hook_info[$hook]['group']) && $include_file = module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']) && module_hook($module, $hook))) { + $implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE; // We added something to the cache, so write it when we are done. $implementations['#write_cache'] = TRUE; } } } else { - foreach ($implementations[$hook] as $module) { + foreach ($implementations[$hook] as $module => $group) { + // If this hook implementation is stored in a lazy-loaded file, so include + // that file first. + if ($group) { + module_load_include('inc', $module, "$module.$group"); + } // It is possible that a module removed a hook implementation without the // implementations cache being rebuilt yet, so we check module_hook() on // each request to avoid undefined function errors. @@ -400,13 +409,45 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) { } } - // The explicit cast forces a copy to be made. This is needed because - // $implementations[$hook] is only a reference to an element of - // $implementations and if there are nested foreaches (due to nested node - // API calls, for example), they would both manipulate the same array's - // references, which causes some modules' hooks not to be called. - // See also http://www.zend.com/zend/art/ref-count.php. - return (array)$implementations[$hook]; + return array_keys($implementations[$hook]); +} + +/** + * Retrieve a list of what hooks are explicitly declared. + */ +function module_hook_info() { + $hook_info = &drupal_static(__FUNCTION__, array()); + + if (empty($hook_info)) { + $cache = cache_get('hook_info'); + if ($cache === FALSE) { + // Rebuild the cache and save it. + // We can't use module_invoke_all() here or it would cause an infinite + // loop. + foreach (module_list() as $module) { + $function = $module . '_hook_info'; + if (function_exists($function)) { + $result = $function(); + if (isset($result) && is_array($result)) { + $hook_info = array_merge_recursive($hook_info, $result); + } + } + } + // We can't use drupal_alter() for the same reason as above. + foreach (module_list() as $module) { + $function = $module . '_hook_info_alter'; + if (function_exists($function)) { + $function($hook_info); + } + } + cache_set('hook_info', $hook_info); + } + else { + $hook_info = $cache->data; + } + } + + return $hook_info; } /** diff --git a/includes/token.inc b/includes/token.inc index 097e77709..47e7201a2 100644 --- a/includes/token.inc +++ b/includes/token.inc @@ -153,7 +153,6 @@ function token_scan($text) { function token_generate($type, array $tokens, array $data = array(), array $options = array()) { $results = array(); $options += array('sanitize' => TRUE); - _token_initialize(); $result = module_invoke_all('tokens', $type, $tokens, $data, $options); foreach ($result as $original => $replacement) { @@ -227,25 +226,8 @@ function token_find_with_prefix(array $tokens, $prefix, $delimiter = ':') { function token_info() { $data = &drupal_static(__FUNCTION__); if (!isset($data)) { - _token_initialize(); $data = module_invoke_all('token_info'); drupal_alter('token_info', $data); } return $data; } - -/** - * Load modulename.tokens.inc for all enabled modules. - */ -function _token_initialize() { - $initialized = &drupal_static(__FUNCTION__); - if (!$initialized) { - foreach (module_list() as $module) { - $filename = DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . "/$module.tokens.inc"; - if (file_exists($filename)) { - include_once $filename; - } - } - $initialized = TRUE; - } -} diff --git a/modules/contact/contact.test b/modules/contact/contact.test index 8b15df4cd..6bbf4d996 100644 --- a/modules/contact/contact.test +++ b/modules/contact/contact.test @@ -174,7 +174,7 @@ class ContactSitewideTestCase extends DrupalWebTestCase { // We are testing the auto-reply, so there should be one e-mail going to the sender. $captured_emails = $this->drupalGetMails(array('id' => 'contact_page_autoreply', 'to' => $email, 'from' => 'foo@example.com')); $this->assertEqual(count($captured_emails), 1, t('Auto-reply e-mail was sent to the sender for category "foo".'), t('Contact')); - $this->assertEqual($captured_emails[0]['body'], $foo_autoreply, t('Auto-reply e-mail body is correct for category "foo".'), t('Contact')); + $this->assertEqual($captured_emails[0]['body'], drupal_html_to_text($foo_autoreply), t('Auto-reply e-mail body is correct for category "foo".'), t('Contact')); // Test the auto-reply for category 'bar'. $email = $this->randomName(32) . '@example.com'; @@ -183,7 +183,7 @@ class ContactSitewideTestCase extends DrupalWebTestCase { // Auto-reply for category 'bar' should result in one auto-reply e-mail to the sender. $captured_emails = $this->drupalGetMails(array('id' => 'contact_page_autoreply', 'to' => $email, 'from' => 'bar@example.com')); $this->assertEqual(count($captured_emails), 1, t('Auto-reply e-mail was sent to the sender for category "bar".'), t('Contact')); - $this->assertEqual($captured_emails[0]['body'], $bar_autoreply, t('Auto-reply e-mail body is correct for category "bar".'), t('Contact')); + $this->assertEqual($captured_emails[0]['body'], drupal_html_to_text($bar_autoreply), t('Auto-reply e-mail body is correct for category "bar".'), t('Contact')); // Verify that no auto-reply is sent when the auto-reply field is left blank. $email = $this->randomName(32) . '@example.com'; diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index 88850e472..e2cb53e0e 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -1107,7 +1107,7 @@ class DrupalWebTestCase extends DrupalTestCase { $language = language_default(); // Use the test mail class instead of the default mail handler class. - variable_set('mail_sending_system', array('default-system' => 'TestingMailSystem')); + variable_set('mail_system', array('default-system' => 'TestingMailSystem')); // Use temporary files directory with the same prefix as the database. $public_files_directory = $this->originalFileDirectory . '/' . $db_prefix; diff --git a/modules/simpletest/simpletest.test b/modules/simpletest/simpletest.test index a26216a6e..3f157d4d1 100644 --- a/modules/simpletest/simpletest.test +++ b/modules/simpletest/simpletest.test @@ -339,7 +339,7 @@ class SimpleTestMailCaptureTestCase extends DrupalWebTestCase { $this->assertEqual(count($captured_emails), 0, t('The captured e-mails queue is empty.'), t('E-mail')); // Send the e-mail. - $response = drupal_mail_sending_system('simpletest', 'drupal_mail_test')->mail($message); + $response = drupal_mail_system('simpletest', 'drupal_mail_test')->mail($message); // Ensure that there is one e-mail in the captured e-mails array. $captured_emails = $this->drupalGetMails(); @@ -360,7 +360,7 @@ class SimpleTestMailCaptureTestCase extends DrupalWebTestCase { 'to' => $this->randomName(32) . '@example.com', 'body' => $this->randomString(512), ); - drupal_mail_sending_system('drupal_mail_test', $index)->mail($message); + drupal_mail_system('drupal_mail_test', $index)->mail($message); } // There should now be 6 e-mails captured. @@ -377,7 +377,7 @@ class SimpleTestMailCaptureTestCase extends DrupalWebTestCase { // Send the last e-mail again, so we can confirm that the drupalGetMails-filter // correctly returns all e-mails with a given property/value. - drupal_mail_sending_system('drupal_mail_test', $index)->mail($message); + drupal_mail_system('drupal_mail_test', $index)->mail($message); $captured_emails = $this->drupalGetMails(array('id' => 'drupal_mail_test_4')); $this->assertEqual(count($captured_emails), 2, t('All e-mails with the same id are returned when filtering by id.'), t('E-mail')); } diff --git a/modules/simpletest/tests/mail.test b/modules/simpletest/tests/mail.test index 641d606ff..5db3abb7e 100644 --- a/modules/simpletest/tests/mail.test +++ b/modules/simpletest/tests/mail.test @@ -25,7 +25,7 @@ class MailTestCase extends DrupalWebTestCase implements MailSystemInterface { parent::setUp(); // Set MailTestCase (i.e. this class) as the SMTP library - variable_set('mail_sending_system', array('default-system' => 'MailTestCase')); + variable_set('mail_system', array('default-system' => 'MailTestCase')); } /** @@ -42,6 +42,21 @@ class MailTestCase extends DrupalWebTestCase implements MailSystemInterface { } /** + * 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) { diff --git a/modules/system/mail.sending.inc b/modules/system/mail.sending.inc index 6533de07d..96db60bb6 100644 --- a/modules/system/mail.sending.inc +++ b/modules/system/mail.sending.inc @@ -3,21 +3,40 @@ /** * @file - * Drupal core implementations of the DrupalMailSendingInterface. + * Drupal core implementations of MailSystemInterface. */ /** - * The default Drupal mail sending library using PHP's mail function. + * The default Drupal mail backend using PHP's mail function. */ class DefaultMailSystem implements MailSystemInterface { /** + * Concatenate and wrap the e-mail body for plain-text mails. + * + * @param $message + * A message array, as described in hook_mail_alter(). + * + * @return + * The formatted $message. + */ + 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 an e-mail message, using Drupal variables and default settings. - * @see http://php.net/manual/en/function.mail.php the PHP function reference - * for mail(). - * @see drupal_mail() for information on how $message is composed. + * + * @see http://php.net/manual/en/function.mail.php + * @see drupal_mail() * * @param $message - * Message array as described by DrupalMailSendingInterface. + * A message array, as described in hook_mail_alter(). * @return * TRUE if the mail was successfully accepted, otherwise FALSE. */ @@ -44,8 +63,7 @@ class DefaultMailSystem implements MailSystemInterface { * * This class is for running tests or for development. */ -class TestingMailSystem implements MailSystemInterface { - +class TestingMailSystem extends DefaultMailSystem implements MailSystemInterface { /** * Accept an e-mail message and store it in a variable. * diff --git a/modules/system/system.api.php b/modules/system/system.api.php index 1f4d1d394..d29a16035 100644 --- a/modules/system/system.api.php +++ b/modules/system/system.api.php @@ -12,6 +12,38 @@ */ /** + * Defines one or more hooks that are exposed by a module. + * + * Normally hooks do not need to be explicitly defined. However, by declaring a + * hook explicitly, a module may define a "group" for it. Modules that implement + * a hook may then place their implementation in either $module.module or in + * $module.$group.inc. If the hook is located in $module.$group.inc, then that + * file will be automatically loaded when needed. + * In general, hooks that are rarely invoked and/or are very large should be + * placed in a separate include file, while hooks that are very short or very + * frequently called should be left in the main module file so that they are + * always available. + * + * @return + * An associative array whose keys are hook names and whose values are an + * associative array containing: + * - group: A string defining the group to which the hook belongs. The module + * system will determine whether a file with the name $module.$group.inc + * exists, and automatically load it when required. + * + * See system_hook_info() for all hook groups defined by Drupal core. + */ +function hook_hook_info() { + $hooks['token_info'] = array( + 'group' => 'tokens', + ); + $hooks['tokens'] = array( + 'group' => 'tokens', + ); + return $hooks; +} + +/** * Inform the base system and the Field API about one or more entity types. * * Inform the system about one or more entity types (i.e., object types that @@ -700,7 +732,7 @@ function hook_image_toolkits() { * * Email messages sent using functions other than drupal_mail() will not * invoke hook_mail_alter(). For example, a contributed module directly - * calling the drupal_mail_sending_system()->mail() or PHP mail() function + * calling the drupal_mail_system()->mail() or PHP mail() function * will not invoke this hook. All core modules use drupal_mail() for * messaging, it is best practice but not manditory in contributed modules. * diff --git a/modules/system/system.module b/modules/system/system.module index cb63659ec..cd35aa8ac 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -278,6 +278,19 @@ function system_rdf_namespaces() { } /** + * Implement hook_hook_info(). + */ +function system_hook_info() { + $hooks['token_info'] = array( + 'group' => 'tokens', + ); + $hooks['tokens'] = array( + 'group' => 'tokens', + ); + return $hooks; +} + +/** * Implement hook_entity_info(). */ function system_entity_info() { @@ -2762,7 +2775,7 @@ function system_mail($key, &$message, $params) { $body = token_replace($context['message'], $context); $message['subject'] .= str_replace(array("\r", "\n"), '', $subject); - $message['body'][] = drupal_html_to_text($body); + $message['body'][] = $body; } function system_message_action_form($context) { diff --git a/modules/user/user.module b/modules/user/user.module index 2d400f54c..8e23ba165 100644 --- a/modules/user/user.module +++ b/modules/user/user.module @@ -2910,8 +2910,7 @@ function user_preferred_language($account, $default = NULL) { * @param $language * Optional language to use for the notification, overriding account language. * @return - * The return value from drupal_mail_sending_system()->mail(), if ends up - * being called. + * The return value from drupal_mail_system()->mail(), if ends up being called. */ function _user_mail_notify($op, $account, $language = NULL) { // By default, we always notify except for canceled and blocked. |