You are here

mandrill.module in Mandrill 7.2

Same filename and directory in other branches
  1. 8 mandrill.module
  2. 6 mandrill.module
  3. 7 mandrill.module

Enables Drupal to send email directly through Mandrill.

File

mandrill.module
View source
<?php

/**
 * @file
 * Enables Drupal to send email directly through Mandrill.
 */
define('MANDRILL_QUEUE', 'mandrill_queue');
define('MANDRILL_EMAIL_REGEX', '/^\\s*(.+?)\\s*<\\s*([^>]+)\\s*>$/');

/**
 * Implements hook_help().
 */
function mandrill_help($path, $arg) {
  $output = '';
  switch ($path) {
    case 'admin/help#mandrill':
      $output = t('Allow for site emails to be sent through Mandrill.');
  }
  return $output;
}

/**
 * Implements hook_menu().
 */
function mandrill_menu() {
  $items = array();
  $items['admin/config/services/mandrill'] = array(
    'title' => 'Mandrill',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'mandrill_admin_settings',
    ),
    'access arguments' => array(
      'administer mandrill',
    ),
    'description' => 'Send emails through the Mandrill transactional email service.',
    'file' => 'mandrill.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/config/services/mandrill/settings'] = array(
    'title' => 'Settings',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => 0,
  );
  $items['admin/config/services/mandrill/test'] = array(
    'title' => 'Send test email',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'mandrill_test_form',
    ),
    'access callback' => 'mandrill_test_access',
    'description' => 'Send a test email using the Mandrill API.',
    'file' => 'mandrill.admin.inc',
    'type' => MENU_LOCAL_TASK,
    'weight' => 1,
  );
  return $items;
}

/**
 * Implements hook_libraries_info().
 */
function mandrill_libraries_info() {
  $libraries['mandrill'] = array(
    'name' => 'Mandrill API',
    'vendor url' => 'https://mandrillapp.com/api/docs',
    'download url' => 'https://bitbucket.org/mailchimp/mandrill-api-php/get/1.0.52.tar.gz',
    'path' => 'src',
    'version arguments' => array(
      'file' => 'composer.json',
      // Version 1.0.52
      'pattern' => '/\\"version": \\"((\\d+)\\.(\\d+)\\.(\\d+))\\",/',
    ),
    'files' => array(
      'php' => array(
        'Mandrill.php',
      ),
    ),
  );
  return $libraries;
}

/**
 * Access callback for sending test email.
 *
 * @return bool
 *   True if current user has access to send test messages
 */
function mandrill_test_access() {
  $a = user_access('administer mandrill');
  $b = variable_get('mandrill_api_key');
  return $a & !empty($b);
}

/**
 * Implements hook_permission().
 */
function mandrill_permission() {
  return array(
    'administer mandrill' => array(
      'title' => t('Administer Mandrill'),
      'description' => t('Perform administration tasks for the Mandrill email service.'),
      "restrict access" => TRUE,
    ),
  );
}

/**
 * Implements hook_cron_queue_info().
 */
function mandrill_cron_queue_info() {
  $queues = array();
  $queues[MANDRILL_QUEUE] = array(
    'worker callback' => 'mandrill_queue_worker_mailsend',
    'time' => variable_get('mandrill_queue_worker_timeout', 15),
  );
  return $queues;
}

/**
 * Sends a queued email.
 * @see mandrill_cron_queue_info()
 */
function mandrill_queue_worker_mailsend($data) {

  // Send the message stored in the queue item.
  mandrill_mailsend($data['message'], $data['function'], $data['args']);
}

/**
 * Implements hook_mail().
 */
function mandrill_mail($key, &$message, $params) {
  if ($key == 'test') {
    $message['subject'] = $params['subject'];
    $message['body'] = $params['body'];
    if ($params['include_attachment']) {
      $message['attachments'][] = drupal_realpath('misc/druplicon.png');
      $message['body'] .= '  ' . t('The Drupal icon is included as an attachment to test the attachment functionality.');
    }
  }
}

/**
 * Abstracts sending of messages, allowing queueing option.
 * 
 * @param array $message
 *   A message array formatted for Mandrill's sending API, plus 2 additional
 *   indexes for the send_function and an array of $args, if needed by the send
 *   function.
 * @param string $function
 *   The name of the function to use to send the message.
 * @param array $args
 *   Array of arguments to pass to the function provided by $function.
 *
 * @return bool
 *   TRUE if no exception thrown
 */
function mandrill_mailsend($message, $function, $args = array()) {
  try {
    if (!function_exists($function)) {
      watchdog('mandrill', 'Error sending email from %from to %to. Function %function not found.', array(
        '%from' => $message['from_email'],
        '%to' => $message['to'][0]['email'],
        '%function' => $function,
      ), WATCHDOG_ERROR);
      return FALSE;
    }
    $params = array(
      $message,
    ) + $args;
    $response = call_user_func_array($function, $params);
    if (!isset($response['status'])) {
      foreach ($response as $result) {

        // Allow other modules to react based on a send result.
        module_invoke_all('mandrill_mailsend_result', $result, $params);
        switch ($result['status']) {
          case "error":
          case "invalid":
          case "rejected":
            if (!variable_get('mandrill_test_mode')) {
              $to = isset($result['email']) ? $result['email'] : 'recipient';
              $status = isset($result['status']) ? $result['status'] : 'message';
              $error_message = isset($result['message']) ? $result['message'] : 'no message';
              $debug = print_r($result, TRUE);
              $severity = WATCHDOG_ERROR;
              $return = FALSE;

              // Since we cannot do much about normal mail bounces do not treat
              // them as errors, just notices.
              if (isset($result['reject_reason']) && in_array($result['reject_reason'], array(
                'hard-bounce',
                'soft-bounce',
                'spam',
              ))) {
                $severity = WATCHDOG_NOTICE;
                $return = TRUE;
              }
              watchdog('mandrill', 'Failed sending email from %from to %to. @status: @message <pre>@debug</pre>', array(
                '%from' => $message['from_email'],
                '%to' => $to,
                '@status' => $status,
                '@message' => $error_message,
                '@debug' => $debug,
              ), $severity);
              return $return;
            }
            return FALSE;
          case "queued":
            watchdog('mandrill', 'Email from %from to %to queued by Mandrill App.', array(
              '%from' => $message['from_email'],
              '%to' => $result['email'],
            ), WATCHDOG_INFO);
            break;
        }
      }
    }
    else {
      watchdog('mandrill', 'Mail send failed with status %status: code %code, %name, %message', array(
        '%status' => $response['status'],
        '%code' => $response['code'],
        '%name' => $response['name'],
        '%message' => $response['message'],
      ), WATCHDOG_WARNING);
      return FALSE;
    }
    return $response;
  } catch (Mandrill_Error $e) {
    watchdog('mandrill', 'Error sending email from %from to %to. @code: @message', array(
      '%from' => $message['from_email'],
      '%to' => $message['to'],
      '@code' => $e
        ->getCode(),
      '@message' => $e
        ->getMessage(),
    ), WATCHDOG_ERROR);
    return FALSE;
  }
}

/**
 * The actual function that calls the API send message.
 * 
 * This is the default function used by mandrill_mailsend().
 * 
 * @param array $message
 *   Associative array containing message data.
 * 
 * @return array
 *   Results of sending the message.
 *
 * @throws Mandrill_Error
 */
function mandrill_sender_plain($message) {
  if ($mailer = mandrill_get_api_object()) {
    return $mailer->messages
      ->send($message);
  }
  else {
    throw new Mandrill_Error('Missing API key.');
  }
}

/**
 * Return Mandrill API object for communication with the mandrill server.
 *
 * @param bool $reset
 *   Pass in TRUE to reset the statically cached object.
 *
 * @return Mandrill|bool
 *   Mandrill Object upon success
 *   FALSE if variable_get('mandrill_api_key') is unset
 */
function mandrill_get_api_object($reset = FALSE) {
  $api =& drupal_static(__FUNCTION__, NULL);
  if ($api === NULL || $reset === TRUE) {
    $library = libraries_load('mandrill');
    if (!$library['installed']) {
      $msg = t('Failed to load Mandrill PHP library. Please refer to the installation requirements.');
      watchdog('mandrill', $msg, NULL, WATCHDOG_ERROR);
      drupal_set_message($msg, 'error');
      return NULL;
    }
    $api_key = variable_get('mandrill_api_key', '');
    $api_timeout = variable_get('mandrill_api_timeout', 60);
    if (empty($api_key)) {
      $msg = t('Failed to load Mandrill API Key. Please check your Mandrill settings.');
      drupal_set_message($msg, 'error');
      return FALSE;
    }

    // We allow the class name to be overridden, following the example of core's
    // mailsystem, in order to use alternate Mandrill classes. The bundled tests
    // use this approach to extend the Mandrill class with a test server.
    $classname = variable_get('mandrill_api_classname', 'DrupalMandrill');
    $api = new $classname($api_key, $api_timeout);
  }
  return $api;
}

/**
 * Display the names of the modules that are using Mailsystem.
 *
 * This is consistent with with Mailsystem's display. In the future, if
 * Mailsystem were to provide an API for their labeling, that should be used.
 *
 * @return array
 *   Array of all module names indexing to their "display" names,
 *   and some special items for non-module values like null, default-system,
 *   and some clarification talked onto the end of the Mandrill module's name.
 */
function mandrill_get_module_key_names() {
  $name_array = array(
    '' => '--none--',
    'default-system' => "Site-wide default",
  );
  $descriptions = array();
  foreach (system_rebuild_module_data() as $item) {
    if ($item->status) {
      $descriptions[$item->name] = (empty($item->info['package']) ? '' : $item->info['package']) . ' » ' . t('!module module', array(
        '!module' => $item->info['name'],
      ));
    }
  }
  asort($descriptions);
  $mailsystem_settings = mailsystem_get();
  unset($mailsystem_settings['default-system']);
  foreach ($mailsystem_settings as $id => $class) {

    // Separate $id into $module and $key.
    $module = $id;
    while ($module && empty($descriptions[$module])) {

      // Remove a key from the end.
      $module = implode('_', explode('_', $module, -1));
    }

    // If an array key of the $mail_system variable is neither "default-system"
    // nor begins with a module name, then it should be unset.
    if (empty($module)) {

      // This shouldn't happen.
    }

    // Set $title to the human-readable module name.
    $title = preg_replace('/^.* » /', '', $descriptions[$module]);
    if ($key = substr($id, strlen($module) + 1)) {
      $title .= " ({$key} key)";
    }
    $name_array[$id] = $title;
  }
  return $name_array;
}

/**
 * Runs the message through the theming system, and produces the correct
 * plain-text message that will be fed to Mandrill.
 */
function mandrill_prepare_message($message) {
  $module = $message['module'];
  $key = $message['key'];
  $to = $message['to'];
  $subject = $message['subject'];
  $body = $message['body'];
  $hook = [
    'mandrill_message__' . $key,
    'mandrill_message__' . $module . '__' . $key,
  ];
  $variables = [
    'module' => $module,
    'key' => $key,
    'recipient' => $to,
    'subject' => $subject,
    'body' => $body,
  ];
  $message['params']['plaintext'] = drupal_html_to_text($body);
  $message['body'] = theme($hook, $variables);
  return $message;
}

/**
 * Implements hook_theme().
 */
function mandrill_theme() {
  $path = drupal_get_path('module', 'mandrill') . '/theme';
  return [
    'mandrill_message' => [
      'variables' => [
        'module' => NULL,
        'key' => NULL,
        'recipient' => NULL,
        'subject' => NULL,
        'body' => NULL,
      ],
      'template' => 'mandrill-message',
      'pattern' => 'mandrill_message__',
      'file' => 'mandrill.theme.inc',
      'mail theme' => TRUE,
      'path' => $path,
    ],
  ];
}

/**
 * Get a list of mandrill template objects.
 *
 * @return array
 *   An of available templates with complete data or NULL if none are available.
 */
function mandrill_get_templates() {

  // Only show the template settings if the mandrill api can be called.
  $templates = NULL;
  try {
    if ($mailer = mandrill_get_api_object()) {
      $templates = $mailer->templates
        ->getList();
    }
  } catch (Mandrill_Error $e) {
    drupal_set_message(t('Mandrill: %message', array(
      '%message' => $e
        ->getMessage(),
    )), 'error');
    watchdog_exception('mandrill', $e);
  }
  return $templates;
}

/**
 * Get a list of subaccounts.
 */
function mandrill_get_subaccounts() {
  $subaccounts = array();
  try {
    if ($mandrill = mandrill_get_api_object()) {
      $subaccounts = $mandrill->subaccounts
        ->getList();
    }
  } catch (Mandrill_Error $e) {
    drupal_set_message(t('Mandrill: %message', array(
      '%message' => $e
        ->getMessage(),
    )), 'error');
    watchdog_exception('mandrill', $e);
  }
  return $subaccounts;
}

/**
 * Get a list of webhooks.
 */
function mandrill_get_webhooks() {
  $webhooks = array();
  try {
    if ($mandrill = mandrill_get_api_object()) {
      $webhooks = $mandrill->webhooks
        ->getList();
    }
  } catch (Mandrill_Error $e) {
    drupal_set_message(t('Mandrill: %message', array(
      '%message' => $e
        ->getMessage(),
    )), 'error');
    watchdog_exception('mandrill', $e);
  }
  return $webhooks;
}

/**
 * Get a list of inbound email domains.
 */
function mandrill_get_inbound_domains() {
  $domains = array();
  try {
    if ($mandrill = mandrill_get_api_object()) {
      $domains = $mandrill->inbound
        ->domains();
    }
    else {
      $domains = FALSE;
    }
  } catch (Mandrill_Error $e) {
    drupal_set_message(t('Mandrill: %message', array(
      '%message' => $e
        ->getMessage(),
    )), 'error');
    watchdog_exception('mandrill', $e);
  }
  return $domains;
}

/**
 * Get a list of inbound email routes for a domain.
 */
function mandrill_get_inbound_routes($domain) {
  $routes = array();
  try {
    if ($mandrill = mandrill_get_api_object()) {
      $routes = $mandrill->inbound
        ->routes($domain);
    }
  } catch (Mandrill_Error $e) {
    drupal_set_message(t('Mandrill: %message', array(
      '%message' => $e
        ->getMessage(),
    )), 'error');
    watchdog_exception('mandrill', $e);
  }
  return $routes;
}

/**
 * Create a new inbound domain.
 */
function mandrill_add_inbound_domain($domain) {
  $result = NULL;
  try {
    if ($mandrill = mandrill_get_api_object()) {
      $result = $mandrill->inbound
        ->addDomain($domain);
    }
  } catch (Mandrill_Error $e) {
    drupal_set_message(t('Mandrill: %message', array(
      '%message' => $e
        ->getMessage(),
    )), 'error');
    watchdog_exception('mandrill', $e);
  }
  return $result;
}

/**
 * Create a new webhook.
 */
function mandrill_add_webhook($path, $events, $description = 'Drupal Webhook') {
  $result = NULL;
  try {
    if ($mandrill = mandrill_get_api_object()) {
      $result = $mandrill->webhooks
        ->add($GLOBALS['base_url'] . '/' . $path, $description, $events);
    }
  } catch (Mandrill_Error $e) {
    drupal_set_message(t('Mandrill: %message', array(
      '%message' => $e
        ->getMessage(),
    )), 'error');
    watchdog_exception('mandrill', $e);
  }
  return $result;
}

/**
 * Update an existing webhook.
 */
function mandrill_update_webhook($id, $path, $events, $description = 'Drupal Webhook') {
  $result = NULL;
  try {
    if ($mandrill = mandrill_get_api_object()) {
      $result = $mandrill->webhooks
        ->update($id, $GLOBALS['base_url'] . '/' . $path, $description, $events);
    }
  } catch (Mandrill_Error $e) {
    drupal_set_message(t('Mandrill: %message', array(
      '%message' => $e
        ->getMessage(),
    )), 'error');
    watchdog_exception('mandrill', $e);
  }
  return $result;
}

/**
 * Delete an existing webhook.
 */
function mandrill_delete_webhook($webhook_id) {
  $result = NULL;
  try {
    if ($mandrill = mandrill_get_api_object()) {
      $result = $mandrill->webhooks
        ->delete($webhook_id);
    }
  } catch (Mandrill_Error $e) {
    drupal_set_message(t('Mandrill: %message', array(
      '%message' => $e
        ->getMessage(),
    )), 'error');
    watchdog_exception('mandrill', $e);
  }
  return $result;
}

/**
 * Delete an inbound domain.
 */
function mandrill_delete_inbound_domain($domain) {
  $result = NULL;
  try {
    if ($mandrill = mandrill_get_api_object()) {
      $result = $mandrill->inbound
        ->deleteDomain($domain);
    }
  } catch (Mandrill_Error $e) {
    drupal_set_message(t('Mandrill: %message', array(
      '%message' => $e
        ->getMessage(),
    )), 'error');
    watchdog_exception('mandrill', $e);
  }
  return $result;
}

/**
 * Create a new inbound route for a domain.
 */
function mandrill_add_inbound_route($domain, $pattern, $url) {
  $route = NULL;
  try {
    if ($mandrill = mandrill_get_api_object()) {
      $route = $mandrill->inbound
        ->addRoute($domain, $pattern, $url);
    }
  } catch (Mandrill_Error $e) {
    drupal_set_message(t('Mandrill: %message', array(
      '%message' => $e
        ->getMessage(),
    )), 'error');
    watchdog_exception('mandrill', $e);
  }
  return $route;
}

/**
 * Helper to return a comma delimited list of mail keys to not log content for.
 *
 * @return string
 *   a comma delimited list of mail keys
 */
function mandrill_mail_key_blacklist() {
  return variable_get('mandrill_mail_key_blacklist', 'user_password_reset');
}

/**
 * Helper to generate an array of recipients.
 *
 * @param $message array
 *   An array containing the email data.
 *
 * @return array
 *   An array of email addresses.
 */
function mandrill_get_to($message) {

  // Turn comma-separated string of recipients into Mandrill-style array.
  // See https://mandrillapp.com/api/docs/messages.html for reference.
  $recipients = mandrill_map_mail_recipients($message['to']);

  // Add "cc" recipients to the list of primary recipients with tag "cc".
  // It will be later handled in Mandrill to set the right headers / recipients.
  if (!empty($message['headers']['Cc'])) {
    $recipients = array_merge($recipients, mandrill_map_mail_recipients($message['headers']['Cc'], 'cc'));
    unset($message['headers']['Cc']);
  }
  return $recipients;
}

/**
 * Parses & creates Mandrill-style array with recipients.
 *
 * @see https://mandrillapp.com/api/docs/messages.html
 *
 * @param $emails string
 *   Comma-separated string with emails.
 *
 * @param string $type string
 *   Type of email. Mandrill supports "to", "cc" and "bcc".
 *
 * @return array
 *   Array of recipients.
 */
function mandrill_map_mail_recipients($emails, $type = 'to') {
  $recipients = array();
  $email_array = explode(',', $emails);
  foreach ($email_array as $email) {
    if (preg_match(MANDRILL_EMAIL_REGEX, $email, $matches)) {
      $recipients[] = array(
        'email' => $matches[2],
        'name' => $matches[1],
        'type' => $type,
      );
    }
    else {
      $recipients[] = array(
        'email' => $email,
        'type' => $type,
      );
    }
  }
  return $recipients;
}

/**
 * Determine if mail should be processed asynchronously.
 *
 * @return bool
 *   True if asyncronous processing is enabled
 */
function mandrill_process_async() {
  return variable_get('mandrill_process_async', FALSE);
}

/**
 * Returns an array containing the from information for a Mandrill message.
 *
 * @param string $from
 *   (Optional) An optional string representing a specific e-mail address from
 *   which a message should be sent. This will be merged in with defaults. It
 *   can be of the form "Name <email@example.com>"
 *
 * @return array
 *   array(
 *     'email' => 'admin@example.com',
 *     'name' => 'My Site',
 *   )
 */
function mandrill_from($from = '') {
  $default = array();
  $provided = array();

  // Parse out the default "from" details.
  $default_from = variable_get('site_mail', ini_get('sendmail_from'));
  $default['email'] = variable_get('mandrill_from', $default_from);
  $default['name'] = variable_get('mandrill_from_name', variable_get('site_name'));

  // If a "from" string was explicitly provided, parse out the details.
  if (preg_match_all('/(?:"?([^"]*)"?\\s)?(?:<?(.+@[^>]+)>?)/', $from, $matches)) {
    if (isset($matches[1][0]) && !empty($matches[1][0])) {
      $provided['name'] = trim($matches[1][0]);
    }
    if (isset($matches[2][0]) && !empty($matches[2][0])) {
      $provided['email'] = $matches[2][0];
    }
  }
  return array_merge($default, $provided);
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function mandrill_form_mailsystem_admin_settings_alter(&$form, &$form_state, $form_id) {
  $form['mailsystem']['mailsystem_theme']['#description'] .= t(" If using MandrillHtmlMailSystem above, you can control your email theming by copying file sites/all/modules/mandrill/theme/mandrill-message.tpl.php into your theme's templates directory. For example to sites/all/themes/theme-name/templates.");
}

Functions

Namesort descending Description
mandrill_add_inbound_domain Create a new inbound domain.
mandrill_add_inbound_route Create a new inbound route for a domain.
mandrill_add_webhook Create a new webhook.
mandrill_cron_queue_info Implements hook_cron_queue_info().
mandrill_delete_inbound_domain Delete an inbound domain.
mandrill_delete_webhook Delete an existing webhook.
mandrill_form_mailsystem_admin_settings_alter Implements hook_form_FORM_ID_alter().
mandrill_from Returns an array containing the from information for a Mandrill message.
mandrill_get_api_object Return Mandrill API object for communication with the mandrill server.
mandrill_get_inbound_domains Get a list of inbound email domains.
mandrill_get_inbound_routes Get a list of inbound email routes for a domain.
mandrill_get_module_key_names Display the names of the modules that are using Mailsystem.
mandrill_get_subaccounts Get a list of subaccounts.
mandrill_get_templates Get a list of mandrill template objects.
mandrill_get_to Helper to generate an array of recipients.
mandrill_get_webhooks Get a list of webhooks.
mandrill_help Implements hook_help().
mandrill_libraries_info Implements hook_libraries_info().
mandrill_mail Implements hook_mail().
mandrill_mailsend Abstracts sending of messages, allowing queueing option.
mandrill_mail_key_blacklist Helper to return a comma delimited list of mail keys to not log content for.
mandrill_map_mail_recipients Parses & creates Mandrill-style array with recipients.
mandrill_menu Implements hook_menu().
mandrill_permission Implements hook_permission().
mandrill_prepare_message Runs the message through the theming system, and produces the correct plain-text message that will be fed to Mandrill.
mandrill_process_async Determine if mail should be processed asynchronously.
mandrill_queue_worker_mailsend Sends a queued email.
mandrill_sender_plain The actual function that calls the API send message.
mandrill_test_access Access callback for sending test email.
mandrill_theme Implements hook_theme().
mandrill_update_webhook Update an existing webhook.

Constants

Namesort descending Description
MANDRILL_EMAIL_REGEX
MANDRILL_QUEUE @file Enables Drupal to send email directly through Mandrill.