Chris@0: alterInfo('mail_backend_info'); Chris@0: $this->setCacheBackend($cache_backend, 'mail_backend_plugins'); Chris@0: $this->configFactory = $config_factory; Chris@0: $this->loggerFactory = $logger_factory; Chris@0: $this->stringTranslation = $string_translation; Chris@0: $this->renderer = $renderer; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Overrides PluginManagerBase::getInstance(). Chris@0: * Chris@0: * Returns an instance of the mail plugin to use for a given message ID. Chris@0: * Chris@0: * The selection of a particular implementation is controlled via the config Chris@0: * 'system.mail.interface', which is a keyed array. The default Chris@0: * implementation is the mail plugin whose ID is the value of 'default' key. A Chris@0: * more specific match first to key and then to module will be used in Chris@0: * preference to the default. To specify a different plugin for all mail sent Chris@0: * by one module, set the plugin ID as the value for the key corresponding to Chris@0: * the module name. To specify a plugin for a particular message sent by one Chris@0: * module, set the plugin ID as the value for the array key that is the Chris@0: * message ID, which is "${module}_${key}". Chris@0: * Chris@0: * For example to debug all mail sent by the user module by logging it to a Chris@0: * file, you might set the variable as something like: Chris@0: * Chris@0: * @code Chris@0: * array( Chris@0: * 'default' => 'php_mail', Chris@0: * 'user' => 'devel_mail_log', Chris@0: * ); Chris@0: * @endcode Chris@0: * Chris@0: * Finally, a different system can be specified for a specific message ID (see Chris@0: * the $key param), such as one of the keys used by the contact module: Chris@0: * Chris@0: * @code Chris@0: * array( Chris@0: * 'default' => 'php_mail', Chris@0: * 'user' => 'devel_mail_log', Chris@0: * 'contact_page_autoreply' => 'null_mail', Chris@0: * ); Chris@0: * @endcode Chris@0: * Chris@0: * Other possible uses for system include a mail-sending plugin that actually Chris@0: * sends (or duplicates) each message to SMS, Twitter, instant message, etc, Chris@0: * or a plugin that queues up a large number of messages for more efficient Chris@0: * bulk sending or for sending via a remote gateway so as to reduce the load Chris@0: * on the local server. Chris@0: * Chris@0: * @param array $options Chris@0: * An array with the following key/value pairs: Chris@0: * - module: (string) The module name which was used by Chris@0: * \Drupal\Core\Mail\MailManagerInterface->mail() to invoke hook_mail(). Chris@0: * - key: (string) A key to identify the email sent. The final message ID Chris@0: * is a string represented as {$module}_{$key}. Chris@0: * Chris@0: * @return \Drupal\Core\Mail\MailInterface Chris@0: * A mail plugin instance. Chris@0: * Chris@0: * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException Chris@0: */ Chris@0: public function getInstance(array $options) { Chris@0: $module = $options['module']; Chris@0: $key = $options['key']; Chris@0: $message_id = $module . '_' . $key; Chris@0: Chris@0: $configuration = $this->configFactory->get('system.mail')->get('interface'); Chris@0: Chris@0: // Look for overrides for the default mail plugin, starting from the most Chris@0: // specific message_id, and falling back to the module name. Chris@0: if (isset($configuration[$message_id])) { Chris@0: $plugin_id = $configuration[$message_id]; Chris@0: } Chris@0: elseif (isset($configuration[$module])) { Chris@0: $plugin_id = $configuration[$module]; Chris@0: } Chris@0: else { Chris@0: $plugin_id = $configuration['default']; Chris@0: } Chris@0: Chris@0: if (empty($this->instances[$plugin_id])) { Chris@0: $this->instances[$plugin_id] = $this->createInstance($plugin_id); Chris@0: } Chris@0: return $this->instances[$plugin_id]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function mail($module, $key, $to, $langcode, $params = [], $reply = NULL, $send = TRUE) { Chris@0: // Mailing can invoke rendering (e.g., generating URLs, replacing tokens), Chris@0: // but e-mails are not HTTP responses: they're not cached, they don't have Chris@0: // attachments. Therefore we perform mailing inside its own render context, Chris@0: // to ensure it doesn't leak into the render context for the HTTP response Chris@0: // to the current request. Chris@0: return $this->renderer->executeInRenderContext(new RenderContext(), function () use ($module, $key, $to, $langcode, $params, $reply, $send) { Chris@0: return $this->doMail($module, $key, $to, $langcode, $params, $reply, $send); Chris@0: }); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Composes and optionally sends an email message. Chris@0: * Chris@0: * @param string $module Chris@0: * A module name to invoke hook_mail() on. The {$module}_mail() hook will be Chris@0: * called to complete the $message structure which will already contain Chris@0: * common defaults. Chris@0: * @param string $key Chris@0: * A key to identify the email sent. The final message ID for email altering Chris@0: * will be {$module}_{$key}. Chris@0: * @param string $to Chris@0: * The email address or addresses where the message will be sent to. The Chris@0: * formatting of this string will be validated with the Chris@0: * @link http://php.net/manual/filter.filters.validate.php PHP email validation filter. @endlink Chris@0: * Some examples are: Chris@0: * - user@example.com Chris@0: * - user@example.com, anotheruser@example.com Chris@0: * - User Chris@0: * - User , Another User Chris@0: * @param string $langcode Chris@0: * Language code to use to compose the email. Chris@0: * @param array $params Chris@0: * (optional) Parameters to build the email. Chris@0: * @param string|null $reply Chris@0: * Optional email address to be used to answer. Chris@0: * @param bool $send Chris@0: * If TRUE, call an implementation of Chris@0: * \Drupal\Core\Mail\MailInterface->mail() to deliver the message, and Chris@0: * store the result in $message['result']. Modules implementing Chris@0: * hook_mail_alter() may cancel sending by setting $message['send'] to Chris@0: * FALSE. Chris@0: * Chris@0: * @return array Chris@0: * The $message array structure containing all details of the message. If Chris@0: * already sent ($send = TRUE), then the 'result' element will contain the Chris@0: * success indicator of the email, failure being already written to the Chris@0: * watchdog. (Success means nothing more than the message being accepted at Chris@0: * php-level, which still doesn't guarantee it to be delivered.) Chris@0: * Chris@0: * @see \Drupal\Core\Mail\MailManagerInterface::mail() Chris@0: */ Chris@0: public function doMail($module, $key, $to, $langcode, $params = [], $reply = NULL, $send = TRUE) { Chris@0: $site_config = $this->configFactory->get('system.site'); Chris@0: $site_mail = $site_config->get('mail'); Chris@0: if (empty($site_mail)) { Chris@0: $site_mail = ini_get('sendmail_from'); Chris@0: } Chris@0: Chris@0: // Bundle up the variables into a structured array for altering. Chris@0: $message = [ Chris@0: 'id' => $module . '_' . $key, Chris@0: 'module' => $module, Chris@0: 'key' => $key, Chris@0: 'to' => $to, Chris@0: 'from' => $site_mail, Chris@0: 'reply-to' => $reply, Chris@0: 'langcode' => $langcode, Chris@0: 'params' => $params, Chris@0: 'send' => TRUE, Chris@0: 'subject' => '', Chris@0: 'body' => [], Chris@0: ]; Chris@0: Chris@0: // Build the default headers. Chris@0: $headers = [ Chris@0: 'MIME-Version' => '1.0', Chris@0: 'Content-Type' => 'text/plain; charset=UTF-8; format=flowed; delsp=yes', Chris@0: 'Content-Transfer-Encoding' => '8Bit', Chris@0: 'X-Mailer' => 'Drupal', Chris@0: ]; Chris@0: // To prevent email from looking like spam, the addresses in the Sender and Chris@0: // Return-Path headers should have a domain authorized to use the Chris@0: // originating SMTP server. Chris@0: $headers['Sender'] = $headers['Return-Path'] = $site_mail; Chris@17: // Make sure the site-name is a RFC-2822 compliant 'display-name'. Chris@17: $headers['From'] = MailHelper::formatDisplayName($site_config->get('name')) . ' <' . $site_mail . '>'; Chris@0: if ($reply) { Chris@0: $headers['Reply-to'] = $reply; Chris@0: } Chris@0: $message['headers'] = $headers; Chris@0: Chris@0: // Build the email (get subject and body, allow additional headers) by Chris@0: // invoking hook_mail() on this module. We cannot use Chris@0: // moduleHandler()->invoke() as we need to have $message by reference in Chris@0: // hook_mail(). Chris@0: if (function_exists($function = $module . '_mail')) { Chris@0: $function($key, $message, $params); Chris@0: } Chris@0: Chris@0: // Invoke hook_mail_alter() to allow all modules to alter the resulting Chris@0: // email. Chris@0: $this->moduleHandler->alter('mail', $message); Chris@0: Chris@0: // Retrieve the responsible implementation for this message. Chris@0: $system = $this->getInstance(['module' => $module, 'key' => $key]); Chris@0: Chris@17: // Attempt to convert relative URLs to absolute. Chris@17: foreach ($message['body'] as &$body_part) { Chris@17: if ($body_part instanceof MarkupInterface) { Chris@17: $body_part = Markup::create(Html::transformRootRelativeUrlsToAbsolute((string) $body_part, \Drupal::request()->getSchemeAndHttpHost())); Chris@17: } Chris@17: } Chris@17: Chris@0: // Format the message body. Chris@0: $message = $system->format($message); Chris@0: Chris@0: // Optionally send email. Chris@0: if ($send) { Chris@0: // The original caller requested sending. Sending was canceled by one or Chris@0: // more hook_mail_alter() implementations. We set 'result' to NULL, Chris@0: // because FALSE indicates an error in sending. Chris@0: if (empty($message['send'])) { Chris@0: $message['result'] = NULL; Chris@0: } Chris@0: // Sending was originally requested and was not canceled. Chris@0: else { Chris@0: // Ensure that subject is plain text. By default translated and Chris@0: // formatted strings are prepared for the HTML context and email Chris@0: // subjects are plain strings. Chris@0: if ($message['subject']) { Chris@0: $message['subject'] = PlainTextOutput::renderFromHtml($message['subject']); Chris@0: } Chris@0: $message['result'] = $system->mail($message); Chris@0: // Log errors. Chris@0: if (!$message['result']) { Chris@0: $this->loggerFactory->get('mail') Chris@0: ->error('Error sending email (from %from to %to with reply-to %reply).', [ Chris@0: '%from' => $message['from'], Chris@0: '%to' => $message['to'], Chris@0: '%reply' => $message['reply-to'] ? $message['reply-to'] : $this->t('not set'), Chris@0: ]); Chris@17: $this->messenger()->addError($this->t('Unable to send email. Contact the site administrator if the problem persists.')); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: return $message; Chris@0: } Chris@0: Chris@0: }