diff --git a/mailsystem.module b/mailsystem.module index 701a263..2ce3685 100644 --- a/mailsystem.module +++ b/mailsystem.module @@ -6,14 +6,6 @@ */ /** -* Implements hook_theme_registry_alter(). -*/ -function mailsystem_theme_registry_alter(&$theme_registry) { - module_load_include('inc', 'mailsystem', 'mailsystem.theme'); - //return mailsystem_theme_theme_registry_alter($theme_registry); -} - -/** * Retrieves the key of the theme used to render the emails. * * @todo Add some kind of hook to let other modules alter this behavior. diff --git a/mailsystem.services.yml b/mailsystem.services.yml new file mode 100644 index 0000000..3538b01 --- /dev/null +++ b/mailsystem.services.yml @@ -0,0 +1,8 @@ +services: + mailsystem.theme.registry: + class: Drupal\Core\Theme\Registry + arguments: ['@app.root', '@cache.default', '@lock', '@module_handler', '@theme_handler', '@theme.initialization'] + tags: + - { name: needs_destruction } + calls: + - [setThemeManager, ['@theme.manager']] diff --git a/mailsystem.theme.inc b/mailsystem.theme.inc deleted file mode 100644 index ae16ab7..0000000 --- a/mailsystem.theme.inc +++ /dev/null @@ -1,84 +0,0 @@ -getActiveTheme()->getName(); - static $recursion_prevention = FALSE; - - // Prevent recursive execution. - if ($recursion_prevention) { - return; - } - $recursion_prevention = TRUE; - $mailsystem_theme = mailsystem_get_mail_theme(); - - // Only take action if the mailsystem theme is not the current theme. - if ($mailsystem_theme != $theme_key) { - $theme_handler = \Drupal::service('theme_handler'); - $themes = $theme_handler->listInfo(); - // Get the mailsystem theme to be used for rendering emails. - if (isset($themes[$mailsystem_theme])) { - $theme = clone $themes[$mailsystem_theme]; - if (isset($theme)) { - // Establish variables for further processing. - $base_theme = array(); - if (isset($theme->base_themes)) { - foreach (array_keys($theme->base_themes) as $base) { - $base_theme[$base] = clone $themes[$base]; - } - } - if (isset($theme->base_theme) && !isset($base_theme[$theme->base_theme])) { - $base_theme[$theme->base_theme] = clone $themes[$theme->base_theme]; - } - if (isset($theme->engine)) { - $theme_engine = $theme->engine; - } - - // Include template files to let _theme_load_registry add preprocess - // functions. - include_once(drupal_get_path('theme', $theme->name) . '/template.php'); - foreach ($base_theme as $base) { - include_once(drupal_get_path('theme', $base->name) . '/template.php'); - } - - // Get the theme_registry cache. - // @todo replace with something like $cache = \Drupal::service('theme.registry')->get(); - $cache = _theme_load_registry($theme, $base_theme, $theme_engine); - - // Change the registry for hooks with a 'mail theme' element. - foreach ($theme_registry as $name => $hook) { - if (!empty($hook['mail theme'])) { - if (isset($cache[$name])) { - $cache[$name]['includes'][] = drupal_get_path('theme', $theme->name) . '/template.php'; - foreach ($base_theme as $base) { - $cache[$name]['includes'][] = drupal_get_path('theme', $base->name) . '/template.php'; - } - // Change the current registry for the new record. - $theme_registry[$name] = $cache[$name]; - } - - // Look for template suggestions. - foreach ($cache as $cache_name => $cache_hook) { - if (strpos($cache_name, $name . '__') !== FALSE) { - $cache_hook['includes'][] = drupal_get_path('theme', $theme->name) . '/template.php'; - foreach ($base_theme as $base) { - $cache_hook['includes'][] = drupal_get_path('theme', $base->name) . '/template.php'; - } - // Change the current registry for the new record. - $theme_registry[$cache_name] = $cache_hook; - } - } - } - } - } - } - } - $recursion_prevention = FALSE; -} diff --git a/src/MailsystemManager.php b/src/MailsystemManager.php index f337036..8f6e8e9 100644 --- a/src/MailsystemManager.php +++ b/src/MailsystemManager.php @@ -12,11 +12,13 @@ use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Mail\MailInterface; use Drupal\Core\Mail\MailManager; use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\Core\Theme\Registry; +use Drupal\Core\Theme\ThemeManager; +use Drupal\Core\Theme\ThemeManagerInterface; /** * Factory for creating mail system objects based on BasePlugin's. @@ -38,11 +40,65 @@ class MailsystemManager extends MailManager { protected $mailsystemConfig; /** + * @var \Drupal\Core\Theme\ThemeManagerInterface + */ + protected $themeManager; + + /** + * @var \Drupal\Core\Theme\Registry + */ + protected $defaultThemeRegistry; + + /** + * @var \Drupal\Core\Theme\Registry + */ + protected $mailThemeRegistry; + + /** * {@inheritdoc} */ - public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, ConfigFactoryInterface $config_factory, LoggerChannelFactoryInterface $logger_factory, TranslationInterface $string_translation) { + public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, ConfigFactoryInterface $config_factory, LoggerChannelFactoryInterface $logger_factory, TranslationInterface $string_translation, ThemeManagerInterface $theme_manager, Registry $default_theme_registry, Registry $mail_theme_registry) { parent::__construct($namespaces, $cache_backend, $module_handler, $config_factory, $logger_factory, $string_translation); $this->mailsystemConfig = $config_factory->get('mailsystem.settings'); + $this->themeManager = $theme_manager; + $this->defaultThemeRegistry = $default_theme_registry; + $this->mailThemeRegistry = $mail_theme_registry; + } + + /** + * {@inheritdoc} + */ + public function mail($module, $key, $to, $langcode, $params = array(), $reply = NULL, $send = TRUE) { + // Switch the theme to the configured mail theme. + $mail_theme = $this->getMailTheme(); + $current_active_theme = $this->themeManager->getActiveTheme(); + if ($mail_theme != $current_active_theme->getName()) { + $this->themeManager->setActiveTheme(\Drupal::service('theme.initialization')->initTheme($mail_theme)); + + // The theme registry returns the same registry object no matter which + // theme is currently active. This works around that by having a duplicate + // service, that is only called when the mail theme is acive. + // @todo: This will not work if this can not be called. Remove this once + // https://www.drupal.org/node/2640962 is committed. + if ($this->themeManager instanceof ThemeManager) { + $this->themeManager->setThemeRegistry($this->mailThemeRegistry); + } + } + + try { + $message = parent::mail($module, $key, $to, $langcode, $params, $reply, $send); + } + finally { + // Revert the active theme, this is done inside a finally block so it is + // executed even if an exception is thrown during sending a mail. + if ($mail_theme != $current_active_theme->getName()) { + $this->themeManager->setActiveTheme($current_active_theme); + if ($this->themeManager instanceof ThemeManager) { + $this->themeManager->setThemeRegistry($this->defaultThemeRegistry); + } + } + } + return $message; } /** @@ -53,8 +109,8 @@ class MailsystemManager extends MailManager { $key = isset($options['key']) ? $options['key'] : ''; return new Adapter( - $this->getPluginInstance($module, $key, self::MAILSYSTEM_TYPE_FORMATTING), - $this->getPluginInstance($module, $key, self::MAILSYSTEM_TYPE_SENDING) + $this->getPluginInstance($module, $key, static::MAILSYSTEM_TYPE_FORMATTING), + $this->getPluginInstance($module, $key, static::MAILSYSTEM_TYPE_SENDING) ); } @@ -111,4 +167,31 @@ class MailsystemManager extends MailManager { return $this->instances[$plugin_id]; } + /** + * Retrieves the key of the theme used to render the emails. + */ + public function getMailTheme() { + $theme = $this->mailsystemConfig->get('theme'); + switch ($theme) { + case 'default': + $theme = $this->configFactory->get('system.theme')->get('default'); + break; + case 'current': + $theme = $this->themeManager->getActiveTheme()->getName(); + break; + case 'domain': + // Fetch the theme for the current domain. + // @todo: Reimplement this as soon as module port or similar module is around. + if (FALSE && \Drupal::moduleHandler()->moduleExists('domain_theme')) { + // Assign the selected theme, based on the active domain. + global $_domain; + $domain_theme = domain_theme_lookup($_domain['domain_id']); + // The above returns -1 on failure. + $theme = ($domain_theme != -1) ? $domain_theme['theme'] : $this->themeManager->getActiveTheme()->getName(); + } + break; + } + return $theme; + } + } diff --git a/src/MailsystemServiceProvider.php b/src/MailsystemServiceProvider.php index 7c615da..e46c598 100644 --- a/src/MailsystemServiceProvider.php +++ b/src/MailsystemServiceProvider.php @@ -29,7 +29,11 @@ class MailsystemServiceProvider implements ServiceProviderInterface, ServiceModi public function alter(ContainerBuilder $container) { // Overrides mail-factory class to use our own mail manager. $container->getDefinition('plugin.manager.mail') - ->setClass('Drupal\mailsystem\MailsystemManager'); + ->setClass('Drupal\mailsystem\MailsystemManager') + ->addArgument(new Reference('theme.manager')) + ->addArgument(new Reference('theme.registry')) + ->addArgument(new Reference('mailsystem.theme.registry')) + ->addArgument(new Reference('theme.initialization')); } } diff --git a/src/Tests/MailsystemTestThemeTest.php b/src/Tests/MailsystemTestThemeTest.php new file mode 100644 index 0000000..28da490 --- /dev/null +++ b/src/Tests/MailsystemTestThemeTest.php @@ -0,0 +1,77 @@ +config = $this->config('mailsystem.settings'); + + + } + + /** + * Tests the mailsystem theme. + */ + public function testMailsystemTheme() { + + // Manually configure the test mail collector implementation to prevent + // tests from sending out emails and collect them in state instead. + // While this should be enforced via settings.php prior to installation, + // some tests expect to be able to test mail system implementations. + $this->config + ->set('defaults.sender', 'test_mail_collector') + ->set('defaults.formatter', 'test_mail_collector') + ->save(); + + // Send an email. + $this->drupalGet('/mailsystem-test/theme'); + $mails = $this->drupalGetMails(); + + // Check the configuration and if the correct theme was used in mails. + $this->assertEqual($this->config->get('theme'), 'current'); + $this->assertTrue(strpos($mails[0]['body'], 'Anonymous (not verified)') !== FALSE); + + // Install the test theme. + \Drupal::service('theme_handler')->install(array('mailsystem_test_theme')); + $this->config->set('theme', 'mailsystem_test_theme')->save(); + + // Send another email. + $this->drupalGet('/mailsystem-test/theme'); + $mails = $this->drupalGetMails(); + + // Check the new configuration and if the correct theme was used in mails. + $this->assertEqual($this->config->get('theme'), 'mailsystem_test_theme'); + $this->assertTrue(strpos($mails[1]['body'], 'Mailsystem test theme') !== FALSE); + } + +} diff --git a/tests/modules/mailsystem_test/mailsystem_test.info.yml b/tests/modules/mailsystem_test/mailsystem_test.info.yml new file mode 100644 index 0000000..c8626be --- /dev/null +++ b/tests/modules/mailsystem_test/mailsystem_test.info.yml @@ -0,0 +1,7 @@ +name: Mailsystem Test +type: module +package: Mail +hidden: true +core: 8.x +dependencies: + - mailsystem diff --git a/tests/modules/mailsystem_test/mailsystem_test.module b/tests/modules/mailsystem_test/mailsystem_test.module new file mode 100644 index 0000000..2da1ed6 --- /dev/null +++ b/tests/modules/mailsystem_test/mailsystem_test.module @@ -0,0 +1,25 @@ +id()); + $username = array('#theme' => 'username', '#account' => $account); + + $message['subject'] = t('Testing mail theme.'); + $message['body'][] = (string) \Drupal::service('renderer')->renderPlain($username); + break; + } +} + + diff --git a/tests/modules/mailsystem_test/mailsystem_test.routing.yml b/tests/modules/mailsystem_test/mailsystem_test.routing.yml new file mode 100644 index 0000000..fe2a6de --- /dev/null +++ b/tests/modules/mailsystem_test/mailsystem_test.routing.yml @@ -0,0 +1,9 @@ +mailsystem_test.theme: + path: '/mailsystem-test/theme' + defaults: + _title: 'Mailsystem theme' + _controller: '\Drupal\mailsystem_test\Controller\MailsystemTestController::sendMail' + options: + no_cache: 'TRUE' + requirements: + _access: 'TRUE' diff --git a/tests/modules/mailsystem_test/src/Controller/MailsystemTestController.php b/tests/modules/mailsystem_test/src/Controller/MailsystemTestController.php new file mode 100644 index 0000000..ed3d49f --- /dev/null +++ b/tests/modules/mailsystem_test/src/Controller/MailsystemTestController.php @@ -0,0 +1,28 @@ +getDefaultLanguage()->getId(); + \Drupal::service('plugin.manager.mail')->mail($module, $key, $to, $langcode); + return new Response('', 204); + } +} diff --git a/tests/src/Unit/MailsystemManagerTest.php b/tests/src/Unit/MailsystemManagerTest.php index fffe9a2..ac4435a 100644 --- a/tests/src/Unit/MailsystemManagerTest.php +++ b/tests/src/Unit/MailsystemManagerTest.php @@ -6,6 +6,8 @@ namespace Drupal\Tests\mailsystem\Unit; +use Drupal\Core\Theme\Registry; +use Drupal\Core\Theme\ThemeManager; use Drupal\mailsystem\MailsystemManager; use Drupal\Tests\UnitTestCase; @@ -60,7 +62,11 @@ class MailsystemManagerTest extends UnitTestCase { $logger_factory = $this->getMock('Drupal\Core\Logger\LoggerChannelFactoryInterface'); $string_translation = $this->getMock('Drupal\Core\StringTranslation\TranslationInterface'); - $this->mailManager = new MailsystemManager($this->getMock('\Traversable'), $this->getMock('\Drupal\Core\Cache\CacheBackendInterface'), $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface'), $this->configFactory, $logger_factory, $string_translation); + $theme_manager = $this->prophesize(ThemeManager::class); + $default_theme_registry = $this->prophesize(Registry::class); + $mail_theme_registry = $this->prophesize(Registry::class); + + $this->mailManager = new MailsystemManager($this->getMock('\Traversable'), $this->getMock('\Drupal\Core\Cache\CacheBackendInterface'), $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface'), $this->configFactory, $logger_factory, $string_translation, $theme_manager->reveal(), $default_theme_registry->reveal(), $mail_theme_registry->reveal()); } public function testGetInstances_Default() { diff --git a/tests/themes/mailsystem_test_theme/mailsystem_test_theme.info.yml b/tests/themes/mailsystem_test_theme/mailsystem_test_theme.info.yml new file mode 100644 index 0000000..e2c9ae4 --- /dev/null +++ b/tests/themes/mailsystem_test_theme/mailsystem_test_theme.info.yml @@ -0,0 +1,16 @@ +name: 'Mailsystem test theme' +type: theme +description: 'Theme for testing mailsystem' +version: VERSION +core: 8.x +regions: + sidebar_first: 'Left sidebar' + sidebar_second: 'Right sidebar' + content: Content + header: Header + footer: Footer + highlighted: Highlighted + help: Help +regions_hidden: + - sidebar_first + - sidebar_second diff --git a/tests/themes/mailsystem_test_theme/templates/username.html.twig b/tests/themes/mailsystem_test_theme/templates/username.html.twig new file mode 100644 index 0000000..43c081c --- /dev/null +++ b/tests/themes/mailsystem_test_theme/templates/username.html.twig @@ -0,0 +1,26 @@ +{# +/** + * @file + * Default theme implementation for displaying a username in a mail. + * + * Available variables: + * - account: The full account information for the user. + * - name: The user's name, sanitized. + * - extra: Additional text to append to the user's name, sanitized. + * - link_path: The path or URL of the user's profile page, home page, + * or other desired page to link to for more information about the user. + * - link_options: Options to pass to the url() function's $options parameter if + * linking the user's name to the user's page. + * - attributes: HTML attributes for the containing element. + * + * @see template_preprocess_username() + * + * @ingroup themeable + */ +#} +

Mailsystem test theme

+{% if link_path -%} + {{ name }}{{ extra }} +{%- else -%} + {{ name }}{{ extra }} +{%- endif -%}