You are here

views_send.module in Views Send 8

Same filename and directory in other branches
  1. 6 views_send.module
  2. 7 views_send.module

The Views Send module.

Views Send allow mass mailing using Views.

File

views_send.module
View source
<?php

/**
 * @file
 *   The Views Send module.
 *
 * Views Send allow mass mailing using Views.
 *
 * @ingroup views_send
 */
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Mail;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Link;
use Drupal\Core\Mail\MailFormatHelper;
use Drupal\Core\Render\Markup;
use Drupal\Core\Url;
use Drupal\filter\Entity\FilterFormat;
use Drupal\user\Entity\User;
use Drupal\views_send\Event\AllMailAddedEvent;
use Drupal\views_send\Event\MailAddedEvent;
use Drupal\views_send\Event\MailSentEvent;
use Drupal\views_send\Plugin\views\field\ViewsSend;

/**
 * e-mail priorities.
 */
define('VIEWS_SEND_PRIORITY_NONE', 0);
define('VIEWS_SEND_PRIORITY_HIGHEST', 1);
define('VIEWS_SEND_PRIORITY_HIGH', 2);
define('VIEWS_SEND_PRIORITY_NORMAL', 3);
define('VIEWS_SEND_PRIORITY_LOW', 4);
define('VIEWS_SEND_PRIORITY_LOWEST', 5);

/**
 * Token pattern.
 */
define('VIEWS_SEND_TOKEN_PATTERN', 'views-send:%s');
define('VIEWS_SEND_TOKEN_PREFIX', '[');
define('VIEWS_SEND_TOKEN_POSTFIX', ']');

/**
 * Detect if there is MIME support (through modules like Mime Mail or Swift Mailer).
 */
switch (TRUE) {
  case \Drupal::moduleHandler()
    ->moduleExists('mailgun'):
  case \Drupal::moduleHandler()
    ->moduleExists('mandrill'):
  case \Drupal::moduleHandler()
    ->moduleExists('mimemail'):
  case \Drupal::moduleHandler()
    ->moduleExists('sendgrid_integration'):
  case \Drupal::moduleHandler()
    ->moduleExists('swiftmailer'):
    define('VIEWS_SEND_MIMEMAIL', TRUE);
    break;
  default:
    define('VIEWS_SEND_MIMEMAIL', FALSE);
}

/**
 * Gets the selector field if it exists on the passed-in view.
 *
 * @return
 *  The field object if found. Otherwise, FALSE.
 */
function _views_send_get_field($view) {
  foreach ($view->field as $field_name => $field) {
    if ($field instanceof ViewsSend) {

      // Add in the view object for convenience.
      $field->view = $view;
      return $field;
    }
  }
  return FALSE;
}

/**
 * Implements the form for the "configure" step.
 */
function views_send_config_form(&$form, &$form_state, $view) {
  $display = $view->storage
    ->id() . ':' . $view->current_display;
  $form['display'] = array(
    '#type' => 'value',
    '#value' => $display,
  );
  $form['from'] = array(
    '#type' => 'details',
    '#title' => t('Sender'),
    '#open' => TRUE,
  );
  $config_basekey = $display . '.uid:' . \Drupal::currentUser()
    ->id();
  $site_config = \Drupal::config('system.site');
  $config = \Drupal::config('views_send.user_settings')
    ->get($config_basekey);
  $form['from']['views_send_from_name'] = array(
    '#type' => 'textfield',
    '#title' => t('Sender\'s name'),
    '#description' => t("Enter the sender's human readable name."),
    '#default_value' => isset($config['from_name']) ? $config['from_name'] : $site_config
      ->get('name'),
    '#maxlen' => 255,
  );
  $form['from']['views_send_from_mail'] = array(
    '#type' => 'textfield',
    '#title' => t('Sender\'s e-mail'),
    '#description' => t("Enter the sender's e-mail address."),
    '#required' => TRUE,
    '#default_value' => isset($config['from_mail']) ? $config['from_mail'] : $site_config
      ->get('mail'),
    '#maxlen' => 255,
  );
  $fields = _views_send_get_fields_and_tokens($view, 'fields');
  $tokens = _views_send_get_fields_and_tokens($view, 'tokens');
  $fields_name_text = _views_send_get_fields_and_tokens($view, 'fields_name_text');
  $fields_options = array_merge(array(
    '' => '<' . t('select') . '>',
  ), $fields);
  $form['views_send_tokens'] = array(
    '#type' => 'value',
    '#value' => $tokens,
  );
  $form['to'] = array(
    '#type' => 'details',
    '#title' => t('Recipients'),
    '#open' => TRUE,
  );
  $form['to']['views_send_to_name'] = array(
    '#type' => 'select',
    '#title' => t('Field used for recipient\'s name'),
    '#description' => t('Select which field from the current view will be used as recipient\'s name.'),
    '#options' => $fields_options,
    '#default_value' => isset($config['to_name']) ? $config['to_name'] : '',
  );
  $form['to']['views_send_to_mail'] = array(
    '#type' => 'select',
    '#title' => t('Field used for recipient\'s e-mail'),
    '#description' => t('Select which field from the current view will be used as recipient\'s e-mail.'),
    '#options' => $fields_options,
    '#default_value' => isset($config['to_mail']) ? $config['to_mail'] : '',
    '#required' => TRUE,
  );
  $form['mail'] = array(
    '#type' => 'details',
    '#title' => t('E-mail content'),
    '#open' => TRUE,
  );
  $form['mail']['views_send_subject'] = array(
    '#type' => 'textfield',
    '#title' => t('Subject'),
    '#description' => t('Enter the e-mail\'s subject. You can use tokens in the subject.'),
    '#maxlen' => 255,
    '#required' => TRUE,
    '#default_value' => isset($config['subject']) ? $config['subject'] : '',
  );
  $saved_message = isset($config['message']) ? $config['message'] : array();
  $form['mail']['views_send_message'] = array(
    '#type' => 'text_format',
    '#format' => isset($saved_message['format']) ? $saved_message['format'] : 'plain_text',
    '#title' => t('Message'),
    '#description' => t('Enter the body of the message. You can use tokens in the message.'),
    '#required' => TRUE,
    '#rows' => 10,
    '#default_value' => isset($saved_message['value']) ? $saved_message['value'] : '',
  );
  $form['mail']['token'] = array(
    '#type' => 'details',
    '#title' => t('Replacements'),
    '#description' => t('You can use the following tokens in the subject or message.'),
  );
  if (!\Drupal::moduleHandler()
    ->moduleExists('token')) {
    $form['mail']['token']['tokens'] = array(
      '#markup' => views_send_token_help($fields_name_text),
    );
  }
  else {
    $form['mail']['token']['views_send'] = array(
      '#type' => 'details',
      '#title' => t('Views Send specific tokens'),
    );
    $form['mail']['token']['views_send']['tokens'] = array(
      '#markup' => views_send_token_help($fields_name_text),
    );
    $form['mail']['token']['general'] = array(
      '#type' => 'details',
      '#title' => t('General tokens'),
    );
    $token_types = array(
      'site',
      'user',
      'node',
      'current-date',
    );
    $form['mail']['token']['general']['tokens'] = array(
      '#theme' => 'token_tree_link',
      '#token_types' => $token_types,
    );
  }
  if (VIEWS_SEND_MIMEMAIL && Drupal::currentUser()
    ->hasPermission('attachments with views_send')) {

    // set the form encoding type
    $form['#attributes']['enctype'] = "multipart/form-data";

    // add a file upload file
    $form['mail']['views_send_attachments'] = array(
      '#type' => 'file',
      '#title' => t('Attachment'),
      '#description' => t('NB! The attached file is stored once per recipient in the database if you aren\'t sending the message directly.'),
    );
  }
  $form['additional'] = array(
    '#type' => 'details',
    '#title' => t('Additional e-mail options'),
  );
  $form['additional']['views_send_priority'] = array(
    '#type' => 'select',
    '#title' => t('Priority'),
    '#options' => array(
      VIEWS_SEND_PRIORITY_NONE => t('none'),
      VIEWS_SEND_PRIORITY_HIGHEST => t('highest'),
      VIEWS_SEND_PRIORITY_HIGH => t('high'),
      VIEWS_SEND_PRIORITY_NORMAL => t('normal'),
      VIEWS_SEND_PRIORITY_LOW => t('low'),
      VIEWS_SEND_PRIORITY_LOWEST => t('lowest'),
    ),
    '#description' => t('Note that e-mail priority is ignored by a lot of e-mail programs.'),
    '#default_value' => isset($config['priority']) ? $config['priority'] : 0,
  );
  $form['additional']['views_send_receipt'] = array(
    '#type' => 'checkbox',
    '#title' => t('Request receipt'),
    '#default_value' => isset($config['receipt']) ? $config['receipt'] : 0,
    '#description' => t('Request a Read Receipt from your e-mails. A lot of e-mail programs ignore these so it is not a definitive indication of how many people have read your message.'),
  );
  $form['additional']['views_send_headers'] = array(
    '#type' => 'textarea',
    '#title' => t('Additional headers'),
    '#description' => t("Additional headers to be send with the message. You'll have to enter one per line. Example:<pre>Reply-To: noreply@example.com\nX-MyCustomHeader: Whatever</pre>"),
    '#rows' => 4,
    '#default_value' => isset($config['headers']) ? $config['headers'] : '',
  );
  $form['views_send_direct'] = array(
    '#type' => 'checkbox',
    '#title' => t('Send the message directly using the Batch API.'),
    '#default_value' => isset($config['direct']) ? $config['direct'] : TRUE,
  );
  $form['views_send_carbon_copy'] = array(
    '#type' => 'checkbox',
    '#title' => t('Send a copy of the message to the sender.'),
    '#default_value' => isset($config['carbon_copy']) ? $config['carbon_copy'] : TRUE,
  );
  $form['views_send_remember'] = array(
    '#type' => 'checkbox',
    '#title' => t('Remember these values for the next time a mass mail is sent.'),
    '#default_value' => isset($config['remember']) ? $config['remember'] : FALSE,
  );
  $query = UrlHelper::filterQueryParameters($_GET, array(
    'q',
  ));
  $url = Url::fromRoute('<current>')
    ->setOption('query', $query);
  $form['actions'] = array(
    '#type' => 'container',
    '#attributes' => array(
      'class' => array(
        'form-actions',
      ),
    ),
    '#weight' => 999,
  );
  $form['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Next', [], [
      'context' => 'views_send: Go to preview',
    ]),
    '#validate' => array(
      'views_send_config_form_validate',
    ),
    '#suffix' => Link::fromTextAndUrl(t('Cancel'), $url)
      ->toString(),
  );
  return $form;
}

/**
 * Validation callback for the "configure" step.
 */
function views_send_config_form_validate($form, &$form_state) {
  $account = Drupal::currentUser();
  $values = $form_state
    ->getValues();
  $build_info = $form_state
    ->getBuildInfo();
  $view = $build_info['args'][0];
  if (!($format = FilterFormat::load($values['views_send_message']['format']))) {
    $form_state
      ->setErrorByName('views_send_message', t('Non-existsing format selected'));
  }
  elseif (!$format
    ->access('use', $account)) {
    $form_state
      ->setErrorByName('views_send_message', t('Illegale format selected'));
  }

  // Check if sender's e-mail is a valid one.
  if (!\Drupal::service('email.validator')
    ->isValid(trim($values['views_send_from_mail']))) {
    $form_state
      ->setErrorByName('views_send_from_mail', t('The sender\'s e-mail is not a valid e-mail address: %mail', array(
      '%mail' => $values['views_send_from_mail'],
    )));
  }

  // Check in the column selected as e-mail contain valid e-mail values.
  if (!empty($values['views_send_to_mail'])) {
    $wrong_addresses = array();
    $to_mail_field = $values['views_send_tokens'][$values['views_send_to_mail']];
    $selection = $form_state
      ->get('selection');
    foreach ($selection as $row_id) {
      $to_mail = _views_send_strip_html($view->style_plugin
        ->getField($row_id, $to_mail_field));
      $to_mail_arr = explode(',', $to_mail);
      foreach ($to_mail_arr as $to_mail) {
        $to_mail = trim($to_mail);
        if (!\Drupal::service('email.validator')
          ->isValid($to_mail)) {
          $wrong_addresses[$row_id] = $to_mail;
          break;
        }
      }
    }
    if (count($wrong_addresses) > 0) {
      if (count($wrong_addresses) == count($selection)) {
        $error_message = t("The field used for recipient's e-mail contains an invalid e-mail address in all selected rows. Maybe choose another field to act as recipient's e-mail?");
      }
      else {
        $error_message = t("The field used for recipient's e-mail contains an invalid e-mail address in @wrong of @total selected rows. Choose another field to act as recipient's e-mail or return to the view and narrow the selection to a subset containing only valid addresses. Bad addresses:", array(
          '@wrong' => count($wrong_addresses),
          '@total' => count($selection),
        ));
        $error_message .= sprintf('<table><tr><th>%s</th><th>%s</th></tr>', t('Row'), t('E-mail address'));
        foreach ($wrong_addresses as $rowid => $wrong_address) {
          $error_message .= sprintf('<tr><td>%s</td><td>%s</td></tr>', $rowid, Html::escape($wrong_address));
        }
        $error_message .= '</table>';
      }
      $error_message_as_markup = new FormattableMarkup($error_message, array());
      $form_state
        ->setErrorByName('views_send_to_mail', $error_message_as_markup);
    }
  }
}

/**
 * Implements the form for the "confirm" step.
 *
 * Allows the user to preview the whole message before sending it.
 */
function views_send_confirm_form(&$form, &$form_state, $view) {

  // TODO: Set title as #markup in stead.
  $form['#title'] = Html::escape(t('Review and confirm the message that is about to be sent'));

  // Values entered in the "config" step.
  $configuration = $form_state
    ->get('configuration');
  $selection = $form_state
    ->get('selection');
  if (!VIEWS_SEND_MIMEMAIL && $configuration['views_send_message']['format'] != 'plain_text') {
    \Drupal::messenger()
      ->addMessage(t("Only plain text is supported in the message. Any HTML will be converted to text. If you want to format the message with HTML, you'll have to install and enable the <a href=':url'>Mime Mail</a> module.", array(
      ':url' => 'http://drupal.org/project/mimemail',
    )));
  }

  // From: parts.
  $from_mail = trim($configuration['views_send_from_mail']);
  $from_name = trim($configuration['views_send_from_name']);
  $form['#attributes']['class'] = array(
    'views-send-preview',
  );
  $form['from'] = array(
    '#type' => 'item',
    '#title' => t('From'),
    '#markup' => '<div class="views-send-preview-value">' . Html::escape(_views_send_format_address($from_mail, $from_name, FALSE)) . '</div>',
  );
  $recipients = array();
  $to_name_field = $configuration['views_send_tokens'][$configuration['views_send_to_name']];
  $to_mail_field = $configuration['views_send_tokens'][$configuration['views_send_to_mail']];
  foreach ($selection as $row_id) {
    $to_name = _views_send_strip_html($view->style_plugin
      ->getField($row_id, $to_name_field));
    $to_mail = _views_send_strip_html($view->style_plugin
      ->getField($row_id, $to_mail_field));
    $to_mail_arr = explode(',', $to_mail);
    foreach ($to_mail_arr as $to_mail) {
      $recipients[] = Html::escape(_views_send_format_address($to_mail, $to_name, FALSE));
    }
  }
  $form['to'] = array(
    '#type' => 'item',
    '#title' => t('To'),
    '#markup' => '<div id="views-send-preview-to" class="views-send-preview-value">' . implode(', ', $recipients) . '</div>',
  );
  $form['subject'] = array(
    '#type' => 'item',
    '#title' => t('Subject'),
    '#markup' => '<div class="views-send-preview-value">' . Html::escape($configuration['views_send_subject']) . '</div>',
  );
  $form['message'] = array(
    '#type' => 'item',
    '#title' => t('Message'),
    '#markup' => '<div id="views-send-preview-message" class="views-send-preview-value">' . check_markup($configuration['views_send_message']['value'], $configuration['views_send_message']['format']) . '</div>',
  );
  $headers = array();
  foreach (_views_send_headers($configuration['views_send_receipt'], $configuration['views_send_priority'], $configuration['views_send_from_mail'], $configuration['views_send_headers']) as $key => $value) {
    $headers[] = Html::escape($key . ': ' . $value);
  }
  $form['headers'] = array(
    '#type' => 'item',
    '#title' => t('Headers'),
    '#markup' => '<div id="views-send-preview-headers" class="views-send-preview-value">' . implode('<br />', $headers) . '</div>',
  );
  if (VIEWS_SEND_MIMEMAIL && !empty($configuration['views_send_attachments']) && Drupal::currentUser()
    ->hasPermission('attachments with views_send')) {
    foreach ($configuration['views_send_attachments'] as $attachment) {
      $attachments[] = Html::escape($attachment
        ->getFilename());
    }
    $form['attachments'] = array(
      '#type' => 'item',
      '#title' => t('Attachments'),
      '#markup' => '<div id="views-send-preview-attachments" class="views-send-preview-value">' . implode('<br />', $attachments) . '</div>',
    );
  }
  $query = UrlHelper::filterQueryParameters($_GET, array(
    'q',
  ));
  $url = Url::fromRoute('<current>')
    ->setOption('query', $query);
  $form['actions'] = array(
    '#type' => 'container',
    '#attributes' => array(
      'class' => array(
        'form-actions',
      ),
    ),
    '#weight' => 999,
  );
  $form['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Send', [], [
      'context' => 'views_send: Send mail',
    ]),
    '#suffix' => Link::fromTextAndUrl(t('Cancel'), $url)
      ->toString(),
  );
  return $form;
}

/**
 * Assembles the email and queues it for sending.
 *
 * Also email sent directly using the Batch API is handled here.
 *
 * @param $params
 *   Data entered in the "config" step of the form.
 * @param $selected_rows
 *   An array with the indexes of the selected views rows.
 * @param $view
 *   The actual view objecy.
 */
function views_send_queue_mail($params, $selected_rows, $view) {
  $account = Drupal::currentUser();
  if (!$account
    ->hasPermission('mass mailing with views_send')) {
    \Drupal::messenger()
      ->addError(t('No mails sent since you aren\'t allowed to send mass mail with Views. (<a href="@permurl">Edit the permission.</a>)', array(
      '@permurl' => Url::fromRoute('user.admin_permissions', [], [
        'fragment' => 'module-views_send',
      ])
        ->toString(),
    )));
    return;
  }
  $formats = filter_formats($account);

  // From: parts.
  $from_mail = trim($params['views_send_from_mail']);
  $from_name = $params['views_send_from_name'];
  $to_mail_key = $params['views_send_tokens'][$params['views_send_to_mail']];
  $to_name_key = $params['views_send_tokens'][$params['views_send_to_name']];
  foreach ($selected_rows as $row_id) {
    $to_mail = _views_send_strip_html($view->style_plugin
      ->getField($row_id, $to_mail_key));
    $to_name = _views_send_strip_html($view->style_plugin
      ->getField($row_id, $to_name_key));
    $subject = $params['views_send_subject'];
    $body = $params['views_send_message']['value'];
    $params['format'] = $params['views_send_message']['format'];

    // This shouldn't happen, but better be 100% sure.

    /* FIXME
       if (!$formats[$params['format']]->access('use', $account)) {
         drupal_set_message(t('No mails sent since an illegale format is selected for the message.'));
         return;
       }
       */
    $body = check_markup($body, $params['format']);

    // Populate row/context tokens.
    $token_keys = $token_values = array();
    foreach ($params['views_send_tokens'] as $field_key => $field_name) {
      $token_keys[] = VIEWS_SEND_TOKEN_PREFIX . sprintf(VIEWS_SEND_TOKEN_PATTERN, $field_name) . VIEWS_SEND_TOKEN_POSTFIX;
      $token_values[] = (string) $view->style_plugin
        ->getField($row_id, $field_name);
    }

    // Views Send specific token replacements
    $subject = str_replace($token_keys, $token_values, $subject);
    $body = str_replace($token_keys, $token_values, $body);

    // Global token replacement, and node/user token replacements
    // if a nid/uid is found in the views result row.
    $data = array();
    if (property_exists($view->result[$row_id], 'uid')) {
      $data['user'] = User::load($view->result[$row_id]->uid);
    }
    if (property_exists($view->result[$row_id], 'nid')) {
      $data['node'] = User::load($view->result[$row_id]->nid);
    }
    $token_service = \Drupal::service('token');
    $subject = $token_service
      ->replace($subject, $data);
    $body = $token_service
      ->replace($body, $data);
    if (!VIEWS_SEND_MIMEMAIL || \Drupal::config('mimemail.settings')
      ->get('format') == 'plain_text') {
      $body = MailFormatHelper::htmlToText($body);
    }
    else {

      // This is needed to avoid escaping of HTML in Swiftmailer.
      $body = Markup::create($body);
    }
    if ($params['format'] == 'plain_text') {
      $plain_format = TRUE;
    }
    else {
      $plain_format = FALSE;
    }

    // We transform receipt, priority in headers,
    // merging them to the user defined headers.
    $headers = _views_send_headers($params['views_send_receipt'], $params['views_send_priority'], $from_mail, $params['views_send_headers']);
    $attachments = !empty($params['views_send_attachments']) ? $params['views_send_attachments'] : array();
    $message = array(
      'uid' => $account
        ->id(),
      'timestamp' => time(),
      'from_name' => $from_name,
      'from_mail' => $from_mail,
      'to_name' => $to_name,
      'to_mail' => $to_mail,
      'subject' => _views_send_strip_html($subject),
      'body' => $body,
      'headers' => $headers,
    );
    if ($params['views_send_direct']) {
      $operations[] = array(
        'views_send_batch_deliver',
        array(
          $message,
          $plain_format,
          $attachments,
        ),
      );
    }
    else {
      _views_send_prepare_mail($message, $plain_format, $attachments);

      // Removing stuff added because Swift Mailer and Mandrill doesn't
      // handle attachments in the format function.
      if (\Drupal::moduleHandler()
        ->moduleExists('swiftmailer') || \Drupal::moduleHandler()
        ->moduleExists('mandrill')) {
        unset($message['params']);
      }

      // Queue the message to the spool table.
      \Drupal::database()
        ->insert('views_send_spool')
        ->fields($message)
        ->execute();
      $event = new MailAddedEvent($message);
      $event_dispatcher = \Drupal::service('event_dispatcher');
      $event_dispatcher
        ->dispatch(MailAddedEvent::EVENT_NAME, $event);
    }
  }
  if ($params['views_send_direct']) {
    if ($params['views_send_carbon_copy']) {
      $message['to_name'] = $from_name;
      $message['to_mail'] = $from_mail;
      $operations[] = array(
        'views_send_batch_deliver',
        array(
          $message,
          $plain_format,
          $attachments,
        ),
      );
    }
    $operations[] = array(
      'views_send_remove_uploaded',
      array(
        $attachments,
      ),
    );
    $batch = array(
      'operations' => $operations,
      'finished' => 'views_send_batch_deliver_finished',
      'progress_message' => t('Sent @current of @total messages.'),
    );
    batch_set($batch);
    \Drupal::messenger()
      ->addMessage(\Drupal::translation()
      ->formatPlural(count($selected_rows), '1 message processed.', '@count messages processed.'));
  }
  else {
    if ($params['views_send_carbon_copy']) {
      $message['to_name'] = $from_name;
      $message['to_mail'] = $from_mail;
      \Drupal::database()
        ->insert('views_send_spool')
        ->fields($message)
        ->execute();
    }
    views_send_remove_uploaded($attachments);
    \Drupal::messenger()
      ->addMessage(\Drupal::translation()
      ->formatPlural(count($selected_rows), '1 message added to the spool.', '@count messages added to the spool.'));
    $event = new AllMailAddedEvent(count($selected_rows));
    $event_dispatcher = \Drupal::service('event_dispatcher');
    $event_dispatcher
      ->dispatch(AllMailAddedEvent::EVENT_NAME, $event);
  }
}

// === Hook implementations ====================================================

/**
 * Implements hook_menu().
 */
function views_send_menu() {
  $items = array();
  $items['admin/config/system/views_send'] = array(
    'type' => MENU_NORMAL_ITEM,
    'title' => 'Views Send',
    'description' => 'Configure Views Send general options.',
    'route_name' => 'views_send.configure',
  );
  return $items;
}

/**
 * Implements hook_theme().
 */
function views_send_theme($existing, $type, $theme, $path) {
  return array(
    'views_send_select_all' => array(
      'variables' => array(),
    ),
  );
}

/**
 * Implements hook_cron().
 */
function views_send_cron() {

  // Load cron functions.
  module_load_include('cron.inc', 'views_send');

  // Send pending messages from spool.
  views_send_send_from_spool();

  // Clear successful sent messages.
  views_send_clear_spool();
}

/**
 * Implements hook_mail().
 */
function views_send_mail($key, &$message, $params) {

  // This is a simple message send. User inputs the content directly.
  if ($key == 'direct') {

    // Set the subject.
    $message['subject'] = $params['subject'];

    // Set the body.
    $message['body'][] = $params['body'];

    // Add additional headers.
    $message['headers'] += $params['headers'];
  }
  elseif ($key == 'node') {

    // Translations, theming, etc...
  }
}

// === Helper functions ========================================================

/**
 * Build header array with priority and receipt confirmation settings.
 *
 * @param $receipt
 *   Boolean: If a receipt is requested.
 * @param $priority
 *   Integer: The message priority.
 * @param $from
 *   String: The sender's e-mail address.
 *
 * @return Header array with priority and receipt confirmation info
 */
function _views_send_headers($receipt, $priority, $from, $additional_headers) {
  $headers = array();

  // If receipt is requested, add headers.
  if ($receipt) {
    $headers['Disposition-Notification-To'] = $from;
    $headers['X-Confirm-Reading-To'] = $from;
  }

  // Add priority if set.
  switch ($priority) {
    case VIEWS_SEND_PRIORITY_HIGHEST:
      $headers['Priority'] = 'High';
      $headers['X-Priority'] = '1';
      $headers['X-MSMail-Priority'] = 'Highest';
      break;
    case VIEWS_SEND_PRIORITY_HIGH:
      $headers['Priority'] = 'urgent';
      $headers['X-Priority'] = '2';
      $headers['X-MSMail-Priority'] = 'High';
      break;
    case VIEWS_SEND_PRIORITY_NORMAL:
      $headers['Priority'] = 'normal';
      $headers['X-Priority'] = '3';
      $headers['X-MSMail-Priority'] = 'Normal';
      break;
    case VIEWS_SEND_PRIORITY_LOW:
      $headers['Priority'] = 'non-urgent';
      $headers['X-Priority'] = '4';
      $headers['X-MSMail-Priority'] = 'Low';
      break;
    case VIEWS_SEND_PRIORITY_LOWEST:
      $headers['Priority'] = 'non-urgent';
      $headers['X-Priority'] = '5';
      $headers['X-MSMail-Priority'] = 'Lowest';
      break;
  }

  // Add general headers.
  $headers['Precedence'] = 'bulk';

  // Add additional headers.
  $additional_headers = trim($additional_headers);
  $additional_headers = str_replace("\r", "\n", $additional_headers);
  $additional_headers = explode("\n", $additional_headers);
  foreach ($additional_headers as $header) {
    $header = trim($header);
    if (!empty($header)) {
      list($key, $value) = explode(': ', $header, 2);
      $headers[$key] = trim($value);
    }
  }
  return $headers;
}

/**
 * Build a formatted e-mail address.
 */
function _views_send_format_address($mail, $name, $encode = TRUE) {

  // Do not format addres on Windows based PHP systems or when $name is empty.
  if (substr(PHP_OS, 0, 3) == 'WIN' || empty($name)) {
    return $mail;
  }
  else {
    $display_name = $encode ? Mail::formatDisplayName($name) : $name;
    return sprintf('"%s" <%s>', $display_name, $mail);
  }
}

/**
 * Prepare the mail message before sending or spooling.
 *
 * @param array $message
 *   which contains the following keys:
 *   from_name
 *     String holding the Sender's name.
 *   from_mail
 *     String holding the Sender's e-mail.
 *   to_name
 *     String holding the Recipient's name.
 *   to_mail
 *     String holding the Recipient's e-mail.
 *   subject
 *     String with the e-mail subject. This argument can be altered here.
 *   body
 *     Text with the e-mail body. This argument can be altered here.
 *   headers
 *     Associative array with e-mail headers. This argument can be altered here.
 * @param boolean $plain_format
 *   Whether the e-mail should be sent in plain format.
 * @param array $attachments
 *   An array with file information objects (as returned by file_save_upload).
 */
function _views_send_prepare_mail(&$message, $plain_format = TRUE, $attachments = array()) {

  // Extract all variables/keys from the message.
  extract($message);

  /**
   * TODO: In the future, this module will be able to send an existing node.
   * $key will have to make the difference. A value when we pickup a node, other
   * when user inputs the subject & body of the message.
   */
  $key = 'direct';

  // Build message parameters.
  $params = array();
  $params['from_name'] = $from_name;
  $params['from_mail'] = $from_mail;
  $params['from_formatted'] = _views_send_format_address($from_mail, $from_name);
  $params['to_name'] = $to_name;
  $params['to_mail'] = $to_mail;
  $to_mail_formatted = array();
  foreach (explode(',', $to_mail) as $addr) {
    $to_mail_formatted[] = _views_send_format_address($addr, $to_name);
  }
  $params['to_formatted'] = implode(', ', $to_mail_formatted);
  $params['subject'] = $subject;
  $params['body'] = $body;
  $params['headers'] = $headers;
  if (VIEWS_SEND_MIMEMAIL) {
    $params['attachments'] = [];
    foreach ($attachments as $attachment) {
      $params['attachments'][] = [
        'filepath' => $attachment
          ->getFileUri(),
        'filename' => $attachment
          ->getFilename(),
        'filemime' => $attachment
          ->getMimeType(),
      ];
    }
    if ($plain_format) {

      // Tell Mimemail module that this is a plain text message as HTML is the default.
      $params['plain'] = TRUE;
    }
  }

  // Call Drupal standard mail function, but without sending.
  $mail = \Drupal::service('plugin.manager.mail')
    ->mail('views_send', $key, $params['to_formatted'], \Drupal::languageManager()
    ->getDefaultLanguage()
    ->getId(), $params, $params['from_formatted'], FALSE);

  // Updating message with data from generated mail
  $message['to_mail'] = $mail['to'];
  $message['from_mail'] = $mail['from'];
  $message['subject'] = $mail['subject'];
  $message['body'] = $mail['body'];
  $message['headers'] = serialize($mail['headers']);

  // Preserving attachments because Swift Mailer and Mandrill doesn't
  // handle attachments in the format function.
  if (!empty($params['attachments']) && (\Drupal::moduleHandler()
    ->moduleExists('mandrill') || \Drupal::moduleHandler()
    ->moduleExists('swiftmailer'))) {
    $attachments = array();
    if (\Drupal::moduleHandler()
      ->moduleExists('mandrill')) {
      foreach ($params['attachments'] as $attachment) {
        $attachments[] = [
          'uri' => $attachment['filepath'],
        ];
      }
    }
    else {
      if (\Drupal::moduleHandler()
        ->moduleExists('swiftmailer')) {
        $attachments = $params['attachments'];
      }
    }
    $message['params'] = array(
      'attachments' => $attachments,
    );
  }
}

/**
 * Sending a prepared message.
 *
 * @return
 *   Boolean indicating if the message was sent successfully.
 */
function views_send_deliver($message) {
  if (is_array($message)) {
    $message = (object) $message;
  }
  $key = 'direct';
  $headers = unserialize($message->headers);
  $mail = array(
    'to' => $message->to_mail,
    'from' => $message->from_mail,
    'subject' => $message->subject,
    'body' => $message->body,
    'headers' => $headers,
  );

  // Adding attachments explicitly because Swift Mailer and Mandrill doesn't
  // handle attachments in the format function. Only works for batch delivery of mail.
  if (!empty($message->params) && (\Drupal::moduleHandler()
    ->moduleExists('swiftmailer') || \Drupal::moduleHandler()
    ->moduleExists('mandrill'))) {
    $mail['params'] = $message->params;
  }
  $system = $mail_backend = \Drupal::service('plugin.manager.mail')
    ->getInstance(array(
    'module' => 'views_send',
    'key' => $key,
  ));
  return $system
    ->mail($mail);
}

/**
 * Preparing and sending a message (coming from a batch job).
 */
function views_send_batch_deliver($message, $plain_format, $attachments, &$context) {
  _views_send_prepare_mail($message, $plain_format, $attachments);
  $status = views_send_deliver($message);
  if ($status) {
    if (\Drupal::config('views_send.settings')
      ->get('debug')) {
      \Drupal::logger('views_send')
        ->notice(t('Message sent to %mail.', array(
        '%mail' => $message['to_mail'],
      )));
    }
    $event = new MailSentEvent($message);
    $event_dispatcher = \Drupal::service('event_dispatcher');
    $event_dispatcher
      ->dispatch(MailSentEvent::EVENT_NAME, $event);
  }
  else {
    $context['results'][] = t('Failed sending message to %mail - spooling it.', array(
      '%mail' => $message['to_mail'],
    ));

    // Queue the message to the spool table.
    \Drupal::database()
      ->insert('views_send_spool')
      ->fields($message)
      ->execute();
    $event = new MailAddedEvent($message);
    $event_dispatcher = \Drupal::service('event_dispatcher');
    $event_dispatcher
      ->dispatch(MailAddedEvent::EVENT_NAME, $event);
  }
}

/**
 * Displays status after sending messages as a batch job.
 */
function views_send_batch_deliver_finished($success, $results, $operations) {
  if ($success) {
    foreach ($results as $result) {
      \Drupal::messenger()
        ->addMessage($result);
    }
  }
}

/**
 * Remove uploaded files.
 *
 * @param $attachments: An array with file entities (coming from file_save_upload).
 */
function views_send_remove_uploaded($attachments) {
  foreach ($attachments as $attachment) {
    $attachment
      ->delete();
  }
}

// === Theming functions =======================================================

/**
 * Theme the replacement tokens.
 *
 * @param $tokens:
 *   Keyed array with tokens as keys and description as values.
 *
 * @return
 *   A themed table with all tokens.
 *
 * @todo: Add help for other tokens
 */
function views_send_token_help($fields) {
  $header = array(
    t('Token'),
    t('Replacement value'),
  );
  $rows = array();
  foreach ($fields as $field => $title) {
    $rows[] = array(
      VIEWS_SEND_TOKEN_PREFIX . sprintf(VIEWS_SEND_TOKEN_PATTERN, $field) . VIEWS_SEND_TOKEN_POSTFIX,
      $title,
    );
  }
  $table = array(
    '#type' => 'table',
    '#header' => $header,
    '#rows' => $rows,
  );
  $output = \Drupal::service('renderer')
    ->render($table);
  return $output;
}
if (\Drupal::moduleHandler()
  ->moduleExists('token')) {

  /**
   * Implements hook_token_info().
   */
  function views_send_token_info() {
    $data = array();
    $fields_name_text = _views_send_get_fields_and_tokens(NULL, 'fields_name_text');
    if ($fields_name_text) {

      // We are in the Views form config
      foreach ($fields_name_text as $field => $title) {
        $data[$field] = array(
          'name' => $title,
          'description' => '',
        );
      }
      $type = array(
        'name' => t('Views Send'),
        'description' => t('Tokens for Views Send.'),
        'needs-data' => 'views-send',
      );
      return array(
        'types' => array(
          'views-send' => $type,
        ),
        'tokens' => array(
          'views-send' => $data,
        ),
      );
    }
    else {
      foreach (_views_send_email_message_property_info() as $key => $info) {
        $data[$key] = array(
          'name' => $info['label'],
          'description' => '',
        );
      }
      $type = array(
        'name' => t('Views Send e-mail message'),
        'description' => t('Tokens for Views Send e-mail message.'),
        'needs-data' => 'views_send_email_message',
      );
      return array(
        'types' => array(
          'views_send_email_message' => $type,
        ),
        'tokens' => array(
          'views_send_email_message' => $data,
        ),
      );
    }
  }

  /**
   * Implementation hook_tokens().
   *
   * These token replacements are used by Rules and not in the Views form.
   */
  function views_send_tokens($type, $tokens, array $data = array(), array $options = array()) {
    $replacements = array();
    if ($type == 'views_send_email_message' && !empty($data['views_send_email_message'])) {
      foreach ($tokens as $name => $original) {
        $replacements[$original] = $data['views_send_email_message']->{$name};
      }
    }
    return $replacements;
  }
}

/**
 * Generates and returns fields and tokens.
 */
function _views_send_get_fields_and_tokens($view, $type) {
  static $return;
  if (isset($return[$type])) {
    return $return[$type];
  }
  if (!in_array($type, array(
    'fields',
    'tokens',
    'fields_name_text',
  )) || !$view) {
    return FALSE;
  }
  $fields = array();
  $tokens = array();
  $fields_name_text = array();
  $enable_excluded_fields = $view->field['views_send_bulk_form']->options['enable_excluded_fields'];
  foreach ($view->field as $field_name => $field) {

    // Ignore Views Send field(s).
    if ($field instanceof ViewsSend) {
      continue;
    }
    elseif (isset($field->options['exclude']) && $field->options['exclude']) {

      // If enable_excluded_fields is false, excluded fields should not be available.
      if (!$enable_excluded_fields) {
        continue;
      }
    }
    if (!empty($field->field)) {
      $field_key = $field->field;
    }
    elseif (property_exists($field, 'field_alias')) {
      $field_key = $field->field_alias;
      if ($field_key == 'unknown') {
        $field_key = $field_name;
      }
    }
    else {
      $field_key = $field_name;
    }

    // Add field position to ensure unique keys.
    $field_key .= '_pos_' . $field->position;
    $field_text = $field
      ->label() . ' (' . $field_name . ')';
    $fields[$field_key] = $field_text;
    $tokens[$field_key] = $field_name;
    $fields_name_text[$field_name] = $field_text;
  }
  $return = array();
  $return['fields'] = $fields;
  $return['tokens'] = $tokens;
  $return['fields_name_text'] = $fields_name_text;
  return $return[$type];
}

/**
 * Returns property info for Views Send Email Message
 */
function _views_send_email_message_property_info() {
  $propertyinfo = array(
    'uid' => array(
      'type' => 'integer',
      'label' => t('User ID'),
    ),
    'timestamp' => array(
      'type' => 'integer',
      'label' => t('Timestamp'),
    ),
    'from_name' => array(
      'type' => 'text',
      'label' => t('Sender\'s name'),
    ),
    'from_mail' => array(
      'type' => 'text',
      'label' => t('Sender\'s e-mail'),
    ),
    'to_name' => array(
      'type' => 'text',
      'label' => t('Recipient\'s name'),
    ),
    'to_mail' => array(
      'type' => 'text',
      'label' => t('Recipient\'s e-mail'),
    ),
    'subject' => array(
      'type' => 'text',
      'label' => t('E-mail subject'),
    ),
    'body' => array(
      'type' => 'text',
      'label' => t('E-mail body'),
    ),
    'headers' => array(
      'type' => 'text',
      'label' => t('E-mail headers (serialized)'),
    ),
  );
  return $propertyinfo;
}

/**
 * Returns the textual content from a HTML string after
 * stripping tags, trimming and decoding HTML entities.
 *
 * Typically used when handling data from getField that 
 * returns HTML (with entities). We have to sanitize the 
 * data before using it in headers.
 */
function _views_send_strip_html($html) {
  return html_entity_decode(trim(strip_tags($html)), ENT_QUOTES);
}

Functions

Namesort descending Description
views_send_batch_deliver Preparing and sending a message (coming from a batch job).
views_send_batch_deliver_finished Displays status after sending messages as a batch job.
views_send_config_form Implements the form for the "configure" step.
views_send_config_form_validate Validation callback for the "configure" step.
views_send_confirm_form Implements the form for the "confirm" step.
views_send_cron Implements hook_cron().
views_send_deliver Sending a prepared message.
views_send_mail Implements hook_mail().
views_send_menu Implements hook_menu().
views_send_queue_mail Assembles the email and queues it for sending.
views_send_remove_uploaded Remove uploaded files.
views_send_theme Implements hook_theme().
views_send_token_help Theme the replacement tokens.
_views_send_email_message_property_info Returns property info for Views Send Email Message
_views_send_format_address Build a formatted e-mail address.
_views_send_get_field Gets the selector field if it exists on the passed-in view.
_views_send_get_fields_and_tokens Generates and returns fields and tokens.
_views_send_headers Build header array with priority and receipt confirmation settings.
_views_send_prepare_mail Prepare the mail message before sending or spooling.
_views_send_strip_html Returns the textual content from a HTML string after stripping tags, trimming and decoding HTML entities.

Constants