diff --git a/core/core.services.yml b/core/core.services.yml index afb710bb05e92c57636176dac45c2d35843efe1b..c3021b6b447fd84b8f701339117256417104eb17 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -587,9 +587,9 @@ services: flood: class: Drupal\Core\Flood\DatabaseBackend arguments: ['@database', '@request'] - mail.factory: - class: Drupal\Core\Mail\MailFactory - arguments: ['@config.factory'] + plugin.manager.mail: + class: Drupal\Core\Mail\MailManager + arguments: ['@container.namespaces', '@cache.cache', '@language_manager', '@module_handler', '@config.factory'] plugin.manager.condition: class: Drupal\Core\Condition\ConditionManager parent: default_plugin_manager diff --git a/core/includes/mail.inc b/core/includes/mail.inc index 6ddff9789a44d2bfcf99f902c2994a3baa2e9014..0a0896d7065bc3569fab1dd43f9dd0012e025f2c 100644 --- a/core/includes/mail.inc +++ b/core/includes/mail.inc @@ -84,7 +84,7 @@ * called to complete the $message structure which will already contain common * defaults. * @param string $key - * A key to identify the e-mail sent. The final e-mail id for e-mail altering + * A key to identify the e-mail sent. The final message ID for e-mail altering * will be {$module}_{$key}. * @param string $to * The e-mail address or addresses where the message will be sent to. The @@ -193,23 +193,23 @@ function drupal_mail($module, $key, $to, $langcode, $params = array(), $reply = } /** - * Returns an object that implements Drupal\Core\Mail\MailInterface. + * Returns an instance of the mail plugin to use for a given message ID. * * @param string $module * The module name which was used by drupal_mail() to invoke hook_mail(). * @param string $key - * A key to identify the e-mail sent. The final e-mail ID for the e-mail + * A key to identify the e-mail sent. The final message ID for the e-mail * alter hook in drupal_mail() would have been {$module}_{$key}. * * @return \Drupal\Core\Mail\MailInterface - * An object that implements Drupal\Core\Mail\MailInterface. + * A mail plugin instance. * - * @throws \Exception + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * - * @see \Drupal\Core\Mail\MailFactory::get() + * @see \Drupal\Core\Mail\MailManager::getInstance() */ function drupal_mail_system($module, $key) { - return \Drupal::service('mail.factory')->get($module, $key); + return \Drupal::service('plugin.manager.mail')->getInstance(array('module' => $module, 'key' => $key)); } /** diff --git a/core/lib/Drupal/Core/Annotation/Mail.php b/core/lib/Drupal/Core/Annotation/Mail.php new file mode 100644 index 0000000000000000000000000000000000000000..abfddd37acb0ee6476711ecef1f55e1b1cee1ab5 --- /dev/null +++ b/core/lib/Drupal/Core/Annotation/Mail.php @@ -0,0 +1,44 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\Annotation\Mail. + */ + +namespace Drupal\Core\Annotation; + +use Drupal\Component\Annotation\Plugin; + +/** + * Defines a Mail annotation object. + * + * @Annotation + */ +class Mail extends Plugin { + + /** + * The plugin ID. + * + * @var string + */ + public $id; + + /** + * The human-readable name of the mail plugin. + * + * @var \Drupal\Core\Annotation\Translation + * + * @ingroup plugin_translatable + */ + public $label; + + /** + * A short description of the mail plugin. + * + * @var \Drupal\Core\Annotation\Translation + * + * @ingroup plugin_translatable + */ + public $description; + +} diff --git a/core/lib/Drupal/Core/Mail/MailFactory.php b/core/lib/Drupal/Core/Mail/MailFactory.php deleted file mode 100644 index 439fd64746dd48efb0c8cde2dbf093e028508dea..0000000000000000000000000000000000000000 --- a/core/lib/Drupal/Core/Mail/MailFactory.php +++ /dev/null @@ -1,126 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\Core\Mail\MailFactory. - */ - -namespace Drupal\Core\Mail; - -use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Component\Utility\String; - -/** - * Factory for creating mail system objects. - */ -class MailFactory { - - /** - * Config object for mail system configurations. - * - * @var \Drupal\Core\Config\Config - */ - protected $mailConfig; - - /** - * List of already instantiated mail system objects. - * - * @var array - */ - protected $instances = array(); - - /** - * Constructs a MailFActory object. - * - * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory - * The configuration factory. - */ - public function __construct(ConfigFactoryInterface $configFactory) { - $this->mailConfig = $configFactory->get('system.mail'); - } - - - /** - * Returns an object that implements \Drupal\Core\Mail\MailInterface. - * - * Allows for one or more custom mail backends to format and send mail messages - * composed using drupal_mail(). - * - * The selection of a particular implementation is controlled via the config - * 'system.mail.interface', which is a keyed array. The default - * implementation is the class whose name is the value of 'default' key. A - * more specific match first to key and then to module will be used in - * preference to the default. To specify a different class for all mail sent - * by one module, set the class name as the value for the key corresponding to - * the module name. To specify a class for a particular message sent by one - * module, set the class name as the value for the array key that is the - * message id, which is "${module}_${key}". - * - * For example to debug all mail sent by the user module by logging it to a - * file, you might set the variable as something like: - * - * @code - * array( - * 'default' => 'Drupal\Core\Mail\PhpMail', - * 'user' => 'Drupal\devel\DevelMailLog', - * ); - * @endcode - * - * Finally, a different system can be specified for a specific e-mail ID (see - * the $key param), such as one of the keys used by the contact module: - * - * @code - * array( - * 'default' => 'Drupal\Core\Mail\PhpMail', - * 'user' => 'Drupal\devel\DevelMailLog', - * 'contact_page_autoreply' => 'Drupal\example\NullMail', - * ); - * @endcode - * - * Other possible uses for system include a mail-sending class that actually - * sends (or duplicates) each message to SMS, Twitter, instant message, etc, - * or a class that queues up a large number of messages for more efficient - * bulk sending or for sending via a remote gateway so as to reduce the load - * on the local server. - * - * @param string $module - * The module name which was used by drupal_mail() to invoke hook_mail(). - * @param string $key - * 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}. - * - * @return \Drupal\Core\Mail\MailInterface - * An object that implements Drupal\Core\Mail\MailInterface. - * - * @throws \Exception - */ - public function get($module, $key) { - $id = $module . '_' . $key; - - $configuration = $this->mailConfig->get('interface'); - - // Look for overrides for the default class, starting from the most specific - // id, and falling back to the module name. - if (isset($configuration[$id])) { - $class = $configuration[$id]; - } - elseif (isset($configuration[$module])) { - $class = $configuration[$module]; - } - else { - $class = $configuration['default']; - } - - if (empty($this->instances[$class])) { - $interfaces = class_implements($class); - if (isset($interfaces['Drupal\Core\Mail\MailInterface'])) { - $this->instances[$class] = new $class(); - } - else { - throw new \Exception(String::format('Class %class does not implement interface %interface', array('%class' => $class, '%interface' => 'Drupal\Core\Mail\MailInterface'))); - } - } - return $this->instances[$class]; - } - -} diff --git a/core/lib/Drupal/Core/Mail/MailInterface.php b/core/lib/Drupal/Core/Mail/MailInterface.php index d3e317351d64fcee1dea4d64aa7bfa898ce7cb90..62a63197ff32a360b4cf4996b9d998170dfdd18a 100644 --- a/core/lib/Drupal/Core/Mail/MailInterface.php +++ b/core/lib/Drupal/Core/Mail/MailInterface.php @@ -17,10 +17,10 @@ interface MailInterface { * * 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 Drupal\Core\Mail\PhpMail 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 Drupal\Core\Mail\PhpMail + * are converted to plain-text by the Drupal\Core\Mail\Plugin\Mail\PhpMail + * implementation. For example, an alternative implementation could override + * the default implementation and also sanitize the HTML for usage in a MIME- + * encoded email, but still invoking the Drupal\Core\Mail\Plugin\Mail\PhpMail * implementation to generate an alternate plain-text version for sending. * * @param array $message diff --git a/core/lib/Drupal/Core/Mail/MailManager.php b/core/lib/Drupal/Core/Mail/MailManager.php new file mode 100644 index 0000000000000000000000000000000000000000..9f012813591b7f7e8647bfb06df128a7da0875b3 --- /dev/null +++ b/core/lib/Drupal/Core/Mail/MailManager.php @@ -0,0 +1,143 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\Mail\MailManager. + */ + +namespace Drupal\Core\Mail; + +use Drupal\Core\Plugin\DefaultPluginManager; +use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Component\Utility\String; +use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException; + +/** + * Mail plugin manager. + */ +class MailManager extends DefaultPluginManager { + + /** + * Config object for mail system configurations. + * + * @var \Drupal\Core\Config\Config + */ + protected $mailConfig; + + /** + * List of already instantiated mail plugins. + * + * @var array + */ + protected $instances = array(); + + /** + * Constructs the MailManager object. + * + * @param \Traversable $namespaces + * An object that implements \Traversable which contains the root paths + * keyed by the corresponding namespace to look for plugin implementations. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend + * Cache backend instance to use. + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager + * The language manager. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler to invoke the alter hook with. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The configuration factory. + */ + public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, ModuleHandlerInterface $module_handler, ConfigFactoryInterface $config_factory) { + parent::__construct('Plugin/Mail', $namespaces, 'Drupal\Core\Annotation\Mail'); + $this->alterInfo($module_handler, 'mail_backend_info'); + $this->setCacheBackend($cache_backend, $language_manager, 'mail_backend_plugins'); + $this->mailConfig = $config_factory->get('system.mail'); + } + + /** + * Overrides PluginManagerBase::getInstance(). + * + * Returns an instance of the mail plugin to use for a given message ID. + * + * The selection of a particular implementation is controlled via the config + * 'system.mail.interface', which is a keyed array. The default + * implementation is the mail plugin whose ID is the value of 'default' key. A + * more specific match first to key and then to module will be used in + * preference to the default. To specify a different plugin for all mail sent + * by one module, set the plugin ID as the value for the key corresponding to + * the module name. To specify a plugin for a particular message sent by one + * module, set the plugin ID as the value for the array key that is the + * message ID, which is "${module}_${key}". + * + * For example to debug all mail sent by the user module by logging it to a + * file, you might set the variable as something like: + * + * @code + * array( + * 'default' => 'php_mail', + * 'user' => 'devel_mail_log', + * ); + * @endcode + * + * Finally, a different system can be specified for a specific message ID (see + * the $key param), such as one of the keys used by the contact module: + * + * @code + * array( + * 'default' => 'php_mail', + * 'user' => 'devel_mail_log', + * 'contact_page_autoreply' => 'null_mail', + * ); + * @endcode + * + * Other possible uses for system include a mail-sending plugin that actually + * sends (or duplicates) each message to SMS, Twitter, instant message, etc, + * or a plugin that queues up a large number of messages for more efficient + * bulk sending or for sending via a remote gateway so as to reduce the load + * on the local server. + * + * @param array $options + * An array with the following key/value pairs: + * - module: (string) The module name which was used by drupal_mail() to + * invoke hook_mail(). + * - key: (string) A key to identify the email sent. The final message ID + * is a string represented as {$module}_{$key}. + * + * @return \Drupal\Core\Mail\MailInterface + * A mail plugin instance. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + */ + public function getInstance(array $options) { + $module = $options['module']; + $key = $options['key']; + $message_id = $module . '_' . $key; + + $configuration = $this->mailConfig->get('interface'); + + // Look for overrides for the default mail plugin, starting from the most + // specific message_id, and falling back to the module name. + if (isset($configuration[$message_id])) { + $plugin_id = $configuration[$message_id]; + } + elseif (isset($configuration[$module])) { + $plugin_id = $configuration[$module]; + } + else { + $plugin_id = $configuration['default']; + } + + if (empty($this->instances[$plugin_id])) { + $plugin = $this->createInstance($plugin_id); + if (is_subclass_of($plugin, '\Drupal\Core\Mail\MailInterface')) { + $this->instances[$plugin_id] = $plugin; + } + else { + throw new InvalidPluginDefinitionException($plugin_id, String::format('Class %class does not implement interface %interface', array('%class' => get_class($plugin), '%interface' => 'Drupal\Core\Mail\MailInterface'))); + } + } + return $this->instances[$plugin_id]; + } +} diff --git a/core/lib/Drupal/Core/Mail/PhpMail.php b/core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php similarity index 90% rename from core/lib/Drupal/Core/Mail/PhpMail.php rename to core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php index 7bf290c10775ebb9463d202e639198921c5c2b58..2d1f3281577e1db61e64ced6fc27278d22c0f713 100644 --- a/core/lib/Drupal/Core/Mail/PhpMail.php +++ b/core/lib/Drupal/Core/Mail/Plugin/Mail/PhpMail.php @@ -2,13 +2,21 @@ /** * @file - * Definition of Drupal\Core\Mail\PhpMail. + * Contains Drupal\Core\Mail\Plugin\Mail\PhpMail. */ -namespace Drupal\Core\Mail; +namespace Drupal\Core\Mail\Plugin\Mail; + +use Drupal\Core\Mail\MailInterface; /** - * The default Drupal mail backend using PHP's mail function. + * Defines the default Drupal mail backend, using PHP's native mail() function. + * + * @Mail( + * id = "php_mail", + * label = @Translation("Default PHP mailer"), + * description = @Translation("Sends the message as plain text, using PHP's native mail() function.") + * ) */ class PhpMail implements MailInterface { diff --git a/core/lib/Drupal/Core/Mail/Plugin/Mail/TestMailCollector.php b/core/lib/Drupal/Core/Mail/Plugin/Mail/TestMailCollector.php new file mode 100644 index 0000000000000000000000000000000000000000..1579e885e52c7d77cd22f06d79983322c0a93fe8 --- /dev/null +++ b/core/lib/Drupal/Core/Mail/Plugin/Mail/TestMailCollector.php @@ -0,0 +1,35 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\Mail\Plugin\Mail\TestMailCollector. + */ + +namespace Drupal\Core\Mail\Plugin\Mail; + +use Drupal\Core\Mail\MailInterface; + +/** + * Defines a mail backend that captures sent messages in the state system. + * + * This class is for running tests or for development. + * + * @Mail( + * id = "test_mail_collector", + * label = @Translation("Mail collector"), + * description = @Translation("Does not send the message, but stores it in Drupal within the state system. Used for testing.") + * ) + */ +class TestMailCollector extends PhpMail implements MailInterface { + + /** + * {@inheritdoc} + */ + public function mail(array $message) { + $captured_emails = \Drupal::state()->get('system.test_mail_collector') ?: array(); + $captured_emails[] = $message; + \Drupal::state()->set('system.test_mail_collector', $captured_emails); + + return TRUE; + } +} diff --git a/core/lib/Drupal/Core/Mail/TestMailCollector.php b/core/lib/Drupal/Core/Mail/TestMailCollector.php deleted file mode 100644 index 3b3e0566e64dd03e301238900a60221aa0687b7e..0000000000000000000000000000000000000000 --- a/core/lib/Drupal/Core/Mail/TestMailCollector.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\Core\Mail\TestMailCollector. - */ - -namespace Drupal\Core\Mail; - -/** - * Defines a mail sending implementation that captures sent messages to the - * state system. - * - * This class is for running tests or for development. - */ -class TestMailCollector extends PhpMail implements MailInterface { - - /** - * Overrides \Drupal\Core\Mail\PhpMail::mail(). - * - * Accepts an e-mail message and stores it with the state system. - */ - public function mail(array $message) { - $captured_emails = \Drupal::state()->get('system.test_mail_collector') ?: array(); - $captured_emails[] = $message; - \Drupal::state()->set('system.test_mail_collector', $captured_emails); - - return TRUE; - } -} diff --git a/core/modules/simpletest/lib/Drupal/simpletest/InstallerTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/InstallerTestBase.php index 3f8d0c4487793df86319b32b760259e3c24c04ba..1da7f9f58a17bf01f2c2e069880d75f731210acd 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/InstallerTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/InstallerTestBase.php @@ -131,7 +131,7 @@ protected function setUp() { // Manually configure the test mail collector implementation to prevent // tests from sending out e-mails and collect them in state instead. \Drupal::config('system.mail') - ->set('interface.default', 'Drupal\Core\Mail\TestMailCollector') + ->set('interface.default', 'test_mail_collector') ->save(); // When running from run-tests.sh we don't get an empty current path which diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 97954558159d2cb55b9faa0a03d579c8dc423df0..9928b68d686318463c110dbe20e2615bb2e18801 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -840,7 +840,7 @@ protected function setUp() { // While this should be enforced via settings.php prior to installation, // some tests expect to be able to test mail system implementations. \Drupal::config('system.mail') - ->set('interface.default', 'Drupal\Core\Mail\TestMailCollector') + ->set('interface.default', 'test_mail_collector') ->save(); // Restore the original Simpletest batch. diff --git a/core/modules/system/config/system.mail.yml b/core/modules/system/config/system.mail.yml index e3eb7685187a5da626b48739485d144e84e45c8c..a7147a7bd947dfed81989e80fed660d62dfc1480 100644 --- a/core/modules/system/config/system.mail.yml +++ b/core/modules/system/config/system.mail.yml @@ -1,2 +1,2 @@ interface: - default: 'Drupal\Core\Mail\PhpMail' + default: 'php_mail' diff --git a/core/modules/system/lib/Drupal/system/Tests/Mail/MailTest.php b/core/modules/system/lib/Drupal/system/Tests/Mail/MailTest.php index 025c190db00c575a76644d7b185900ce87c59535..56aef7f98ae008caec2015c7ab20855c5a2c732c 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Mail/MailTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Mail/MailTest.php @@ -8,20 +8,19 @@ namespace Drupal\system\Tests\Mail; use Drupal\Core\Language\Language; -use Drupal\Core\Mail\MailInterface; use Drupal\simpletest\WebTestBase; /** - * Defines a mail class used for testing. + * Tests related to the mail system. */ -class MailTest extends WebTestBase implements MailInterface { +class MailTest extends WebTestBase { /** * Modules to enable. * * @var array */ - public static $modules = array('simpletest'); + public static $modules = array('simpletest', 'system_mail_failure_test'); /** * The most recent message that was sent through the test case. @@ -39,24 +38,19 @@ public static function getInfo() { ); } - function setUp() { - parent::setUp(); - - // Set MailTestCase (i.e. this class) as the SMTP library - \Drupal::config('system.mail')->set('interface.default', 'Drupal\system\Tests\Mail\MailTest')->save(); - } - /** * Assert that the pluggable mail system is functional. */ public function testPluggableFramework() { - $language_interface = \Drupal::languageManager()->getCurrentLanguage(); + // Switch mail backends. + \Drupal::config('system.mail')->set('interface.default', 'test_php_mail_failure')->save(); - // Use MailTestCase for sending a message. - drupal_mail('simpletest', 'mail_test', 'testing@example.com', $language_interface->id); + // Get the default MailInterface class instance. + $mail_backend = drupal_mail_system('default', 'default'); - // Assert whether the message was sent through the send function. - $this->assertEqual(self::$sent_message['to'], 'testing@example.com', 'Pluggable mail system is extendable.'); + // Assert whether the default mail backend is an instance of the expected + // class. + $this->assertTrue($mail_backend instanceof \Drupal\system_mail_failure_test\Plugin\Mail\TestPhpMailFailure, 'Pluggable mail system is extendable.'); } /** @@ -67,14 +61,19 @@ public function testPluggableFramework() { public function testCancelMessage() { $language_interface = \Drupal::languageManager()->getCurrentLanguage(); - // Reset the class variable holding a copy of the last sent message. - self::$sent_message = NULL; + // Use the state system collector mail backend. + \Drupal::config('system.mail')->set('interface.default', 'test_mail_collector')->save(); + // Reset the state variable that holds sent messages. + \Drupal::state()->set('system.test_mail_collector', array()); // Send a test message that simpletest_mail_alter should cancel. drupal_mail('simpletest', 'cancel_test', 'cancel@example.com', $language_interface->id); + // Retrieve sent message. + $captured_emails = \Drupal::state()->get('system.test_mail_collector'); + $sent_message = end($captured_emails); // Assert that the message was not actually sent. - $this->assertNull(self::$sent_message, 'Message was canceled.'); + $this->assertFalse($sent_message, 'Message was canceled.'); } /** @@ -83,45 +82,28 @@ public function testCancelMessage() { public function testFromAndReplyToHeader() { $language = \Drupal::languageManager()->getCurrentLanguage(); - // Reset the class variable holding a copy of the last sent message. - self::$sent_message = NULL; + // Use the state system collector mail backend. + \Drupal::config('system.mail')->set('interface.default', 'test_mail_collector')->save(); + // Reset the state variable that holds sent messages. + \Drupal::state()->set('system.test_mail_collector', array()); // Send an e-mail with a reply-to address specified. $from_email = 'Drupal <simpletest@example.com>'; $reply_email = 'someone_else@example.com'; drupal_mail('simpletest', 'from_test', 'from_test@example.com', $language, array(), $reply_email); - // Test that the reply-to 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'], 'Message is sent from the site email account.'); - $this->assertEqual($reply_email, self::$sent_message['headers']['Reply-to'], 'Message reply-to headers are set.'); - $this->assertFalse(isset(self::$sent_message['headers']['Errors-To']), 'Errors-to header must not be set, it is deprecated.'); + // Test that the reply-to e-mail is just the e-mail and not the site name + // and default sender e-mail. + $captured_emails = \Drupal::state()->get('system.test_mail_collector'); + $sent_message = end($captured_emails); + $this->assertEqual($from_email, $sent_message['headers']['From'], 'Message is sent from the site email account.'); + $this->assertEqual($reply_email, $sent_message['headers']['Reply-to'], 'Message reply-to headers are set.'); + $this->assertFalse(isset($sent_message['headers']['Errors-To']), 'Errors-to header must not be set, it is deprecated.'); - self::$sent_message = NULL; // Send an e-mail and check that the From-header contains the site name. drupal_mail('simpletest', 'from_test', 'from_test@example.com', $language); - $this->assertEqual($from_email, self::$sent_message['headers']['From'], 'Message is sent from the site email account.'); - $this->assertFalse(isset(self::$sent_message['headers']['Reply-to']), 'Message reply-to is not set if not specified.'); - $this->assertFalse(isset(self::$sent_message['headers']['Errors-To']), 'Errors-to header must not be set, it is deprecated.'); - } - - /** - * Concatenate and wrap the e-mail body for plain-text mails. - * - * @see \Drupal\Core\Mail\PhpMail - */ - 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; + $captured_emails = \Drupal::state()->get('system.test_mail_collector'); + $sent_message = end($captured_emails); + $this->assertEqual($from_email, $sent_message['headers']['From'], 'Message is sent from the site email account.'); + $this->assertFalse(isset($sent_message['headers']['Reply-to']), 'Message reply-to is not set if not specified.'); + $this->assertFalse(isset($sent_message['headers']['Errors-To']), 'Errors-to header must not be set, it is deprecated.'); } } diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index 19ea5b1b75e2772116296b021305f5fe76bcded1..843629e3201185e0d6822a9356038389e18343c0 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -2559,6 +2559,19 @@ function hook_archiver_info_alter(&$info) { $info['tar']['extensions'][] = 'tgz'; } +/** + * Alter the list of mail backend plugin definitions. + * + * @param array $info + * The mail backend plugin definitions to be altered. + * + * @see \Drupal\Core\Annotation\Mail + * @see \Drupal\Core\Mail\MailManager + */ +function hook_mail_backend_info_alter(&$info) { + unset($info['test_mail_collector']); +} + /** * Alters theme operation links. * diff --git a/core/modules/system/tests/modules/system_mail_failure_test/lib/Drupal/system_mail_failure_test/Plugin/Mail/TestPhpMailFailure.php b/core/modules/system/tests/modules/system_mail_failure_test/lib/Drupal/system_mail_failure_test/Plugin/Mail/TestPhpMailFailure.php new file mode 100644 index 0000000000000000000000000000000000000000..a4c6a3461c390bb16a6ef957a96756c38bfd5c36 --- /dev/null +++ b/core/modules/system/tests/modules/system_mail_failure_test/lib/Drupal/system_mail_failure_test/Plugin/Mail/TestPhpMailFailure.php @@ -0,0 +1,37 @@ +<?php + +/** + * @file + * Contains \Drupal\system_mail_failure_test\Plugin\Mail\TestPhpMailFailure. + */ + +namespace Drupal\system_mail_failure_test\Plugin\Mail; + +use Drupal\Core\Mail\Plugin\Mail\PhpMail; +use Drupal\Core\Mail\MailInterface; + +/** + * Defines a mail sending implementation that always fails. + * + * This class is for running tests or for development. To use set the + * configuration: + * @code + * \Drupal::config('system.mail')->set('interface.default', 'test_php_mail_failure')->save(); + * @endcode + * + * @Mail( + * id = "test_php_mail_failure", + * label = @Translation("Malfunctioning mail backend"), + * description = @Translation("An intentionally broken mail backend, used for tests.") + * ) + */ +class TestPhpMailFailure extends PhpMail implements MailInterface { + + /** + * {@inheritdoc} + */ + public function mail(array $message) { + // Simulate a failed mail send by returning FALSE. + return FALSE; + } +} diff --git a/core/modules/system/tests/modules/system_mail_failure_test/lib/Drupal/system_mail_failure_test/TestPhpMailFailure.php b/core/modules/system/tests/modules/system_mail_failure_test/lib/Drupal/system_mail_failure_test/TestPhpMailFailure.php deleted file mode 100644 index f20472a60a7753d20c7c2643a12a41cc09533540..0000000000000000000000000000000000000000 --- a/core/modules/system/tests/modules/system_mail_failure_test/lib/Drupal/system_mail_failure_test/TestPhpMailFailure.php +++ /dev/null @@ -1,31 +0,0 @@ -<?php - -/** - * @file - * Contains \Drupal\system_mail_failure_test\TestPhpMailFailure. - */ - -namespace Drupal\system_mail_failure_test; - -use Drupal\Core\Mail\PhpMail; -use Drupal\Core\Mail\MailInterface; - -/** - * Defines a mail sending implementation that returns false. - * - * This class is for running tests or for development. To use set the - * configuration: - * @code - * \Drupal::config('system.mail')->set('interface.default', 'Drupal\system_mail_failure_test\TestPhpMailFailure')->save(); - * @endcode - */ -class TestPhpMailFailure extends PhpMail implements MailInterface { - - /** - * Overrides Drupal\Core\Mail\PhpMail::mail(). - */ - public function mail(array $message) { - // Instead of attempting to send a message, just return failure. - return FALSE; - } -} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserCreateFailMailTest.php b/core/modules/user/lib/Drupal/user/Tests/UserCreateFailMailTest.php index 6909667af8476aa37b0295bdc39dbec320e0f2c0..5a7dabe8fb82cd91aed88892b5420dea3b2ddbde 100644 --- a/core/modules/user/lib/Drupal/user/Tests/UserCreateFailMailTest.php +++ b/core/modules/user/lib/Drupal/user/Tests/UserCreateFailMailTest.php @@ -37,7 +37,7 @@ protected function testUserAdd() { $this->drupalLogin($user); // Replace the mail functionality with a fake, malfunctioning service. - \Drupal::config('system.mail')->set('interface.default', 'Drupal\system_mail_failure_test\TestPhpMailFailure')->save(); + \Drupal::config('system.mail')->set('interface.default', 'test_php_mail_failure')->save(); // Create a user, but fail to send an email. $name = $this->randomName(); $edit = array( diff --git a/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php b/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9ef8d20ab2d31c3e6135295778e93ee4afb04bfa --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Mail/MailManagerTest.php @@ -0,0 +1,157 @@ +<?php +/** + * @file + * Contains \Drupal\Tests\Core\Mail\MailManagerTest. + */ + +namespace Drupal\Tests\Core\Mail; + +use Drupal\Tests\UnitTestCase; +use Drupal\Core\Mail\MailManager; +use Drupal\Component\Plugin\Discovery\DiscoveryInterface; + +/** + * Tests the mail plugin manager. + * + * @group Drupal + * @group Mail + * + * @see \Drupal\Core\Mail\MailManager + */ +class MailManagerTest extends UnitTestCase { + + /** + * The cache backend to use. + * + * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $cache; + + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $languageManager; + + /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $moduleHandler; + + /** + * The configuration factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $configFactory; + + /** + * The plugin discovery. + * + * @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $discovery; + + /** + * A list of mail plugin definitions. + * + * @var array + */ + protected $definitions = array( + 'php_mail' => array( + 'id' => 'php_mail', + 'class' => 'Drupal\Core\Mail\Plugin\Mail\PhpMail', + ), + 'test_mail_collector' => array( + 'id' => 'test_mail_collector', + 'class' => 'Drupal\Core\Mail\Plugin\Mail\TestMailCollector', + ), + ); + + /** + * {@inheritdoc} + */ + public static function getInfo() { + return array( + 'name' => 'Mail manager test', + 'description' => 'Tests the mail plugin manager.', + 'group' => 'Mail', + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + // Prepare the default constructor arguments required by MailManager. + $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); + + $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); + $this->languageManager->expects($this->any()) + ->method('getCurrentLanguage') + ->will($this->returnValue((object) array('id' => 'en'))); + + $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); + + // Mock a Discovery object to replace AnnotationClassDiscovery. + $this->discovery = $this->getMock('Drupal\Component\Plugin\Discovery\DiscoveryInterface'); + $this->discovery->expects($this->any()) + ->method('getDefinitions') + ->will($this->returnValue($this->definitions)); + } + + /** + * Sets up the mail manager for testing. + */ + protected function setUpMailManager($interface = array()) { + // Use the provided config for system.mail.interface settings. + $this->configFactory = $this->getConfigFactoryStub(array('system.mail' => array( + 'interface' => $interface, + ))); + // Construct the manager object and override its discovery. + $this->mailManager = new TestMailManager(new \ArrayObject(), $this->cache, $this->languageManager, $this->moduleHandler, $this->configFactory); + $this->mailManager->setDiscovery($this->discovery); + } + + /** + * Tests the getInstance method. + * + * @covers \Drupal\Core\Mail\MailManager::getInstance() + */ + public function testGetInstance() { + $interface = array( + 'default' => 'php_mail', + 'example_testkey' => 'test_mail_collector', + ); + $this->setUpMailManager($interface); + + // Test that an unmatched message_id returns the default plugin instance. + $options = array('module' => 'foo', 'key' => 'bar'); + $instance = $this->mailManager->getInstance($options); + $this->assertInstanceOf('Drupal\Core\Mail\Plugin\Mail\PhpMail', $instance); + + // Test that a matching message_id returns the specified plugin instance. + $options = array('module' => 'example', 'key' => 'testkey'); + $instance = $this->mailManager->getInstance($options); + $this->assertInstanceOf('Drupal\Core\Mail\Plugin\Mail\TestMailCollector', $instance); + } +} + +/** + * Provides a testing version of MailManager with an empty constructor. + */ +class TestMailManager extends MailManager { + /** + * Sets the discovery for the manager. + * + * @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $discovery + * The discovery object. + */ + public function setDiscovery(DiscoveryInterface $discovery) { + $this->discovery = $discovery; + } +}