Browse Source

updated views_send views_bulk_update views_data_export

Bachir Soussi Chiadmi 7 years ago
parent
commit
542ac42fca
42 changed files with 2939 additions and 264 deletions
  1. 3 3
      sites/all/modules/contrib/mail/views_send/views_send.info
  2. 12 5
      sites/all/modules/contrib/mail/views_send/views_send.js
  3. 66 99
      sites/all/modules/contrib/mail/views_send/views_send.module
  4. 45 0
      sites/all/modules/contrib/mail/views_send/views_send.tokens.inc
  5. 19 6
      sites/all/modules/contrib/views/views_bulk_operations/actions/archive.action.inc
  6. 65 0
      sites/all/modules/contrib/views/views_bulk_operations/actions/change_owner.action.inc
  7. 35 0
      sites/all/modules/contrib/views/views_bulk_operations/actions/delete.action.inc
  8. 69 35
      sites/all/modules/contrib/views/views_bulk_operations/actions/modify.action.inc
  9. 23 11
      sites/all/modules/contrib/views/views_bulk_operations/actions/user_cancel.action.inc
  10. 3 3
      sites/all/modules/contrib/views/views_bulk_operations/actions_permissions.info
  11. 8 6
      sites/all/modules/contrib/views/views_bulk_operations/css/views_bulk_operations.css
  12. 44 10
      sites/all/modules/contrib/views/views_bulk_operations/js/views_bulk_operations.js
  13. 2 1
      sites/all/modules/contrib/views/views_bulk_operations/views/views_bulk_operations.views.inc
  14. 26 30
      sites/all/modules/contrib/views/views_bulk_operations/views/views_bulk_operations_handler_field_operations.inc
  15. 1 1
      sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.drush.inc
  16. 4 4
      sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.info
  17. 35 11
      sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.module
  18. 15 5
      sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.rules.inc
  19. BIN
      sites/all/modules/contrib/views/views_data_export/images/csv.png
  20. BIN
      sites/all/modules/contrib/views/views_data_export/images/doc.png
  21. BIN
      sites/all/modules/contrib/views/views_data_export/images/txt.png
  22. BIN
      sites/all/modules/contrib/views/views_data_export/images/xls.png
  23. BIN
      sites/all/modules/contrib/views/views_data_export/images/xml.png
  24. 93 28
      sites/all/modules/contrib/views/views_data_export/plugins/views_data_export_plugin_display_export.inc
  25. 3 1
      sites/all/modules/contrib/views/views_data_export/views_data_export.drush.inc
  26. 3 3
      sites/all/modules/contrib/views/views_data_export/views_data_export.info
  27. 6 1
      sites/all/modules/contrib/views/views_data_export/views_data_export.install
  28. 1 1
      sites/all/modules/contrib/views/views_data_export/views_data_export.module
  29. 43 0
      sites/all/modules/contrib/views/views_send/CHANGELOG.txt
  30. 339 0
      sites/all/modules/contrib/views/views_send/LICENSE.txt
  31. 65 0
      sites/all/modules/contrib/views/views_send/README.txt
  32. 15 0
      sites/all/modules/contrib/views/views_send/views/views_send.views.inc
  33. 60 0
      sites/all/modules/contrib/views/views_send/views/views_send_handler_field_selector.inc
  34. 50 0
      sites/all/modules/contrib/views/views_send/views_send.admin.inc
  35. 77 0
      sites/all/modules/contrib/views/views_send/views_send.cron.inc
  36. 63 0
      sites/all/modules/contrib/views/views_send/views_send.css
  37. 16 0
      sites/all/modules/contrib/views/views_send/views_send.info
  38. 151 0
      sites/all/modules/contrib/views/views_send/views_send.install
  39. 53 0
      sites/all/modules/contrib/views/views_send/views_send.js
  40. 1325 0
      sites/all/modules/contrib/views/views_send/views_send.module
  41. 56 0
      sites/all/modules/contrib/views/views_send/views_send.rules.inc
  42. 45 0
      sites/all/modules/contrib/views/views_send/views_send.tokens.inc

+ 3 - 3
sites/all/modules/contrib/mail/views_send/views_send.info

@@ -8,9 +8,9 @@ core = 7.x
 files[] = views_send.rules.inc
 files[] = views/views_send_handler_field_selector.inc
 
-; Information added by Drupal.org packaging script on 2016-11-09
-version = "7.x-1.3"
+; Information added by Drupal.org packaging script on 2017-04-03
+version = "7.x-1.6"
 core = "7.x"
 project = "views_send"
-datestamp = "1478685242"
+datestamp = "1491222486"
 

+ 12 - 5
sites/all/modules/contrib/mail/views_send/views_send.js

@@ -1,4 +1,11 @@
 (function ($) {
+  // Polyfill for jQuery less than 1.6.
+  if (typeof $.fn.prop != 'function') {
+    jQuery.fn.extend({
+      prop: jQuery.fn.attr
+    });
+  }
+
   Drupal.behaviors.viewsSend = {
     attach: function(context) {
       $('.views-send-selection-form', context).each(function() {
@@ -14,7 +21,7 @@
     // This is the "select all" checkbox in (each) table header.
     $('.views-send-table-select-all', form).click(function() {
       var table = $(this).closest('table')[0];
-      $('input[id^="edit-views-send"]:not(:disabled)', table).attr('checked', this.checked).change();
+      $('input[id^="edit-views-send"]:not(:disabled)', table).prop('checked', this.checked).change();
     });
   }
 
@@ -23,21 +30,21 @@
     $('.views-send-select-all-markup', form).show();
 
     $('.views-send-select-this-page', form).click(function() {
-      $('input[id^="edit-views-send"]', form).attr('checked', this.checked).change();
+      $('input[id^="edit-views-send"]', form).prop('checked', this.checked).change();
 
       // Toggle the "select all" checkbox in grouped tables (if any).
-      $('.views-send-table-select-all', form).attr('checked', this.checked).change();
+      $('.views-send-table-select-all', form).prop('checked', this.checked).change();
     });
 
     $('.views-send-select', form).click(function() {
       // If a checkbox was deselected, uncheck any "select all" checkboxes.
       if (!this.checked) {
-        $('.views-send-select-this-page', form).attr('checked', false).change();
+        $('.views-send-select-this-page', form).prop('checked', false).change();
 
         var table = $(this).closest('table')[0];
         if (table) {
           // Uncheck the "select all" checkbox in the table header.
-          $('.views-send-table-select-all', table).attr('checked', false).change();
+          $('.views-send-table-select-all', table).prop('checked', false).change();
         }
       }
     });

+ 66 - 99
sites/all/modules/contrib/mail/views_send/views_send.module

@@ -33,21 +33,20 @@ define('VIEWS_SEND_TOKEN_PREFIX', '[');
 define('VIEWS_SEND_TOKEN_POSTFIX', ']');
 
 /**
- * Detect if there is MIME support (thorough modules like Mime Mail or Mandrill).
+ * Detect if there is MIME support (through modules like Mime Mail or Mandrill).
  */
-define('VIEWS_SEND_MIMEMAIL', module_exists('mimemail') || module_exists('mandrill'));
-
-/**
- * Sets the mailsystem for Views Send (if not already set).
- */
-function _views_send_mailsystem_set($key) {
-  $mailsystem = mailsystem_get();
-  $mailsystem_key = "views_send_$key";
-  if (empty($mailsystem[$mailsystem_key])) {
-    mailsystem_set(array(
-      $mailsystem_key => $mailsystem['default-system']
-    ));
-  }
+switch (true) {
+  case (module_exists('htmlmail') && module_exists('mailmime')):
+  case module_exists('mailgun'):
+  case module_exists('mandrill'):
+  case module_exists('mimemail'):
+  case module_exists('sendgrid_integration'):
+  case module_exists('swiftmailer'):
+    define('VIEWS_SEND_MIMEMAIL', TRUE);
+    break;
+
+  default:
+    define('VIEWS_SEND_MIMEMAIL', FALSE);
 }
 
 /**
@@ -549,7 +548,7 @@ function views_send_confirm_form($form, &$form_state, $view, $output) {
   );
   if (VIEWS_SEND_MIMEMAIL && !empty($configuration['views_send_attachments']) && user_access('attachments with views_send')) {
     foreach ($configuration['views_send_attachments'] as $attachment) {
-      $attachments[] = $attachment['filename'];
+      $attachments[] = check_plain($attachment['filename']);
     }
     $form['attachments'] = array(
       '#type' => 'item',
@@ -609,6 +608,7 @@ function views_send_form_submit($form, &$form_state) {
         }
       }
       $form_state['configuration'] = $form_state['values'];
+      $form_state['configuration']['views_send_attachments'] = array();
 
       // If a file was uploaded, process it.
       if (VIEWS_SEND_MIMEMAIL && user_access('attachments with views_send') && isset($_FILES['files']) && is_uploaded_file($_FILES['files']['tmp_name']['views_send_attachments'])) {
@@ -665,6 +665,8 @@ function views_send_form_back_submit($form, &$form_state) {
 /**
  * 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
@@ -684,8 +686,6 @@ function views_send_queue_mail($params, $selected_rows, $view) {
     return;
   }
 
-  $formats = filter_formats();
-
   // From: parts.
   $from_mail = trim($params['views_send_from_mail']);
   $from_name = $params['views_send_from_name'];
@@ -699,6 +699,36 @@ function views_send_queue_mail($params, $selected_rows, $view) {
     $to_name_key = false;
     $to_name = '';
   }
+
+  $subject = $params['views_send_subject'];
+  $body = $params['views_send_message']['value'];
+  $headers = _views_send_headers($params['views_send_receipt'], $params['views_send_priority'], $from_mail, $params['views_send_headers']);
+  $format = $params['views_send_message']['format'];
+  $attachments = $params['views_send_attachments'];
+
+  $formats = filter_formats();
+  if (!filter_access($formats[$format])) {
+    drupal_set_message(t('No mails sent since an illegale format is selected for the message.'));
+    return;
+  }
+  else {
+    $body = check_markup($body, $format);
+  }
+
+  if ($format == 'plain_text') {
+    $plain_format = TRUE;
+  }
+  else {
+    $plain_format = FALSE;
+  }
+
+  $message_base = array(
+    'uid' => $user->uid,
+    'from_name' => trim($from_name),
+    'from_mail' => trim($from_mail),
+    'headers' => $headers,
+  );
+
   foreach ($selected_rows as $selected_rows_key => $row_id) {
     // To: parts.
     $to_mail = implode(',', _views_send_get_field_value_from_views_row($view, $row_id, $to_mail_key, 'mail'));
@@ -706,18 +736,6 @@ function views_send_queue_mail($params, $selected_rows, $view) {
       $to_name = _views_send_get_field_value_from_views_row($view, $row_id, $to_name_key, 'plain_text');
     }
 
-    $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.
-    if (!filter_access($formats[$params['format']])) {
-      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) {
@@ -726,8 +744,8 @@ function views_send_queue_mail($params, $selected_rows, $view) {
     }
 
     // Views Send specific token replacements
-    $subject = str_replace($token_keys, $token_values, $subject);
-    $body = str_replace($token_keys, $token_values, $body);
+    $subject_expanded = str_replace($token_keys, $token_values, $subject);
+    $body_expanded = 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.
@@ -738,36 +756,19 @@ function views_send_queue_mail($params, $selected_rows, $view) {
     if (property_exists($view->result[$row_id], 'nid')) {
       $data['node'] = node_load($view->result[$row_id]->nid);
     }
-    $subject = token_replace($subject, $data);
-    $body = token_replace($body, $data);
+    $subject_expanded = token_replace($subject_expanded, $data);
+    $body_expanded = token_replace($body_expanded, $data);
 
     if (!VIEWS_SEND_MIMEMAIL || (variable_get('mimemail_format', 'plain_text') == 'plain_text')) {
-      $body = drupal_html_to_text($body);
+      $body_expanded = drupal_html_to_text($body_expanded);
     }
 
-    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 = isset($params['views_send_attachments']) ?  $params['views_send_attachments'] : array();
-
-    $message = array(
-      'uid' => $user->uid,
+    $message = $message_base + array(
       'timestamp' => time(),
-      'from_name' => trim($from_name),
-      'from_mail' => trim($from_mail),
       'to_name' => trim($to_name),
       'to_mail' => trim($to_mail),
-      'subject' => strip_tags($subject),
-      'body' => $body,
-      'headers' => $headers,
+      'subject' => strip_tags($subject_expanded),
+      'body' => $body_expanded,
     );
 
     // Enable other modules to alter the actual message before queueing it
@@ -782,6 +783,10 @@ function views_send_queue_mail($params, $selected_rows, $view) {
       // Only queue the message if it hasn't been cancelled by another module.
       if ($message['send']) {
         unset($message['send']);
+        // Removing stuff add to work around Swifth Mailer issue 2841663
+        if (module_exists('swiftmailer')) {
+          unset($message['params']);
+        }
 
         db_insert('views_send_spool')->fields($message)->execute();
         if (module_exists('rules')) {
@@ -1086,7 +1091,6 @@ function _views_send_prepare_mail(&$message, $plain_format=TRUE, $attachments=ar
   $params['headers'] = $headers;
 
   if (VIEWS_SEND_MIMEMAIL) {
-    _views_send_mailsystem_set($key);
     $params['attachments'] = $attachments;
     if ($plain_format) {
       $params['plain'] = TRUE;
@@ -1109,6 +1113,10 @@ function _views_send_prepare_mail(&$message, $plain_format=TRUE, $attachments=ar
   $message['body'] = $mail['body'];
   $message['send'] = $mail['send'];
   $message['headers'] = serialize($mail['headers']);
+  // Working around a Swifth Mailer issue - see https://www.drupal.org/node/2841663
+  if (module_exists('swiftmailer')) {
+    $message['params'] = array('attachments' => $params['attachments']);
+  }
 }
 
 /**
@@ -1135,9 +1143,9 @@ function views_send_deliver($message) {
     'body' => $message->body,
     'headers' => $headers,
   );
-
-  if (VIEWS_SEND_MIMEMAIL) {
-    _views_send_mailsystem_set($key);
+  // Because of Swifth Mailer issue 2841663
+  if (module_exists('swiftmailer')) {
+    $mail['params'] = $message->params;
   }
 
   // Mime encode the subject before passing to the mail function 
@@ -1217,47 +1225,6 @@ function theme_views_send_token_help($fields) {
   return $output;
 }
 
-if (module_exists('token')) {
-  /**
-   * Implements hook_token_info().
-   *
-   * These token are used by Rules and not in the Views form.
-   */
-  function views_send_token_info() {
-    $data = array();
-    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.
  */

+ 45 - 0
sites/all/modules/contrib/mail/views_send/views_send.tokens.inc

@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Token integration for the Views Send module.
+ */
+
+/**
+ * Implements hook_token_info().
+ *
+ * These token are used by Rules and not in the Views form.
+ */
+function views_send_token_info() {
+  $data = array();
+  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;
+}

+ 19 - 6
sites/all/modules/contrib/views/views_bulk_operations/actions/archive.action.inc

@@ -3,10 +3,14 @@
 /**
  * @file
  * Provides an action for creating a zip archive of selected files.
+ *
  * An entry in the {file_managed} table is created for the newly created archive,
  * and it is marked as permanent or temporary based on the operation settings.
  */
 
+/**
+ * Implements hook_action_info().
+ */
 function views_bulk_operations_archive_action_info() {
   $actions = array();
   if (function_exists('zip_open')) {
@@ -71,6 +75,10 @@ function views_bulk_operations_archive_action($file, $context) {
     $archive_file->filemime = file_get_mimetype($destination);
     $archive_file->uid      = $user->uid;
     $archive_file->status   = $context['settings']['temporary'] ? FALSE : FILE_STATUS_PERMANENT;
+    // Clear filesize() cache to avoid private file system differences in
+    // filesize.
+    // @see https://www.drupal.org/node/2743999
+    clearstatcache();
     file_save($archive_file);
 
     $url = file_create_url($archive_file->uri);
@@ -100,8 +108,11 @@ function views_bulk_operations_archive_action_form($context) {
 }
 
 /**
- * Assembles a sanitized and unique URI for the archive, and returns it for
- * usage by the action callback (views_bulk_operations_archive_action).
+ * Assembles a sanitized and unique URI for the archive.
+ *
+ * @returns array
+ *   A URI array used by the action callback
+ *   (views_bulk_operations_archive_action).
  */
 function views_bulk_operations_archive_action_submit($form, $form_state) {
   // Validate the scheme, fallback to public if it's somehow invalid.
@@ -156,10 +167,12 @@ function views_bulk_operations_archive_action_views_bulk_operations_form($option
 /**
  * Create a sanitized and unique version of the provided filename.
  *
- * @param $filename
- *   String filename
+ * @param string $filename
+ *   The filename to create.
+ * @param array $archive_list
+ *   The list of files already in the archive.
  *
- * @return
+ * @return string
  *   The new filename.
  */
 function _views_bulk_operations_archive_action_create_filename($filename, $archive_list) {
@@ -167,7 +180,7 @@ function _views_bulk_operations_archive_action_create_filename($filename, $archi
   // some filesystems, not many applications handle them well.
   $filename = preg_replace('/[\x00-\x1F]/u', '_', $filename);
   if (substr(PHP_OS, 0, 3) == 'WIN') {
-    // These characters are not allowed in Windows filenames
+    // These characters are not allowed in Windows filenames.
     $filename = str_replace(array(':', '*', '?', '"', '<', '>', '|'), '_', $filename);
   }
 

+ 65 - 0
sites/all/modules/contrib/views/views_bulk_operations/actions/change_owner.action.inc

@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * @file
+ * Implements a generic entity change owner action.
+ */
+
+/**
+ * Implements hook_action_info().
+ */
+function views_bulk_operations_change_owner_action_info() {
+  return array(
+    'views_bulk_operations_change_owner_action' => array(
+      'type' => 'entity',
+      'label' => t('Change owner'),
+      'configurable' => TRUE,
+      'behavior' => array('changes_property'),
+      'triggers' => array('any'),
+    ),
+  );
+}
+
+/**
+ * Action function.
+ */
+function views_bulk_operations_change_owner_action($entity, $context) {
+  $entity->uid = $context['owner_uid'];
+}
+
+/**
+ * Action form function.
+ */
+function views_bulk_operations_change_owner_action_form($context, &$form_state) {
+  $form['owner_username'] = array(
+    '#type' => 'textfield',
+    '#maxlength' => USERNAME_MAX_LENGTH,
+    '#title' => t('Owner'),
+    '#required' => TRUE,
+    '#description' => t('Choose the user you would like to set as the owner.'),
+    '#autocomplete_path' => 'user/autocomplete',
+  );
+
+  return $form;
+}
+
+/**
+ * Action form validate function.
+ *
+ * Checks that the submitted text is a valid username.
+ */
+function views_bulk_operations_change_owner_action_validate($form, $form_state) {
+  if (!user_load_by_name($form_state['values']['owner_username'])) {
+    form_set_error('owner_username', t('Valid username required.'));
+  }
+}
+
+/**
+ * Action form submit function.
+ *
+ * Pass submitted username back to views_bulk_operations_change_owner.
+ */
+function views_bulk_operations_change_owner_action_submit($form, $form_state) {
+  $user = user_load_by_name($form_state['values']['owner_username']);
+  return array('owner_uid' => $user->uid);
+}

+ 35 - 0
sites/all/modules/contrib/views/views_bulk_operations/actions/delete.action.inc

@@ -24,11 +24,46 @@ function views_bulk_operations_delete_action_info() {
   );
 }
 
+function views_bulk_operations_delete_item_views_bulk_operations_form($settings) {
+  $form = array();
+  $form['log'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Log individual deletions'),
+    '#description' => t('Note: Deleting large amounts of entities will generate large amounts of log messages.'),
+    '#default_value' => !empty($settings['log']),
+  );
+
+  return $form;
+}
+
 function views_bulk_operations_delete_item($entity, $context) {
   $info = entity_get_info($context['entity_type']);
   $entity_id = $entity->{$info['entity keys']['id']};
 
   entity_delete($context['entity_type'], $entity_id);
+
+  // Add a message to the watchdog if we've been configured to do so.
+  if (!empty($context['settings']['log'])) {
+    // Log an appropriate message for this entity type, using the format from
+    // the node, taxonomy and user module for their entity types.
+    switch ($context['entity_type']) {
+      case 'node':
+        watchdog('content', '@type: deleted %title.', array('@type' => $entity->type, '%title' => $entity->title));
+        break;
+
+      case 'taxonomy_term':
+        watchdog('taxonomy', 'Deleted term %name.', array('%name' => $entity->name), WATCHDOG_NOTICE);
+        break;
+
+      case 'user':
+        watchdog('user', 'Deleted user: %name %email.', array('%name' => $entity->name, '%email' => '<' . $entity->mail . '>'), WATCHDOG_NOTICE);
+        break;
+
+      default:
+        watchdog('entity', 'Deleted @type %label.', array('@type' => $context['entity_type'], '%label' => entity_label($context['entity_type'], $entity)));
+        break;
+    }
+  }
 }
 
 function views_bulk_operations_delete_revision($entity, $context) {

+ 69 - 35
sites/all/modules/contrib/views/views_bulk_operations/actions/modify.action.inc

@@ -1,24 +1,30 @@
 <?php
 
 /**
- * @file VBO action to modify entity values (properties and fields).
+ * @file
+ * VBO action to modify entity values (properties and fields).
  */
 
 // Specifies that all available values should be shown to the user for editing.
 define('VBO_MODIFY_ACTION_ALL', '_all_');
 
+/**
+ * Implements hook_action_info().
+ */
 function views_bulk_operations_modify_action_info() {
-  return array('views_bulk_operations_modify_action' => array(
-    'type' => 'entity',
-    'label' => t('Modify entity values'),
-    'behavior' => array('changes_property'),
-    // This action only works when invoked through VBO. That's why it's
-    // declared as non-configurable to prevent it from being shown in the
-    // "Create an advanced action" dropdown on admin/config/system/actions.
-    'configurable' => FALSE,
-    'vbo_configurable' => TRUE,
-    'triggers' => array('any'),
-  ));
+  return array(
+    'views_bulk_operations_modify_action' => array(
+      'type' => 'entity',
+      'label' => t('Modify entity values'),
+      'behavior' => array('changes_property'),
+      // This action only works when invoked through VBO. That's why it's
+      // declared as non-configurable to prevent it from being shown in the
+      // "Create an advanced action" dropdown on admin/config/system/actions.
+      'configurable' => FALSE,
+      'vbo_configurable' => TRUE,
+      'triggers' => array('any'),
+    ),
+  );
 }
 
 /**
@@ -28,7 +34,7 @@ function views_bulk_operations_modify_action_info() {
  * replacing the existing values, or appending to them (based on user input).
  */
 function views_bulk_operations_modify_action($entity, $context) {
-  list(,,$bundle_name) = entity_extract_ids($context['entity_type'], $entity);
+  list(,, $bundle_name) = entity_extract_ids($context['entity_type'], $entity);
   // Handle Field API fields.
   if (!empty($context['selected']['bundle_' . $bundle_name])) {
     // The pseudo entity is cloned so that changes to it don't get carried
@@ -38,7 +44,7 @@ function views_bulk_operations_modify_action($entity, $context) {
       // Get this field's language. We can just pull it from the pseudo entity
       // as it was created using field_attach_form and entity_language so it's
       // already been figured out if this field is translatable or not and
-      // applied the appropriate language code to the field
+      // applied the appropriate language code to the field.
       $language = key($pseudo_entity->{$key});
       // Replace any tokens that might exist in the field columns.
       foreach ($pseudo_entity->{$key}[$language] as $delta => &$item) {
@@ -58,9 +64,11 @@ function views_bulk_operations_modify_action($entity, $context) {
         if ($field_info['cardinality'] != FIELD_CARDINALITY_UNLIMITED && $field_count > $field_info['cardinality']) {
           $entity_label = entity_label($context['entity_type'], $entity);
           $warning = t('Tried to set !field_count values for field !field_name that supports a maximum of !cardinality.',
-                       array('!field_count' => $field_count,
-                             '!field_name' => $field_info['field_name'],
-                             '!cardinality' => $field_info['cardinality']));
+            array(
+              '!field_count' => $field_count,
+              '!field_name' => $field_info['field_name'],
+              '!cardinality' => $field_info['cardinality'],
+            ));
           drupal_set_message($warning, 'warning', FALSE);
         }
 
@@ -76,11 +84,17 @@ function views_bulk_operations_modify_action($entity, $context) {
   }
 
   // Handle properties.
+  // Use the wrapper to set property values, since some properties need
+  // additional massaging by their setter callbacks.
+  // The wrapper will automatically modify $entity itself.
+  $wrapper = entity_metadata_wrapper($context['entity_type'], $entity);
+  // The default setting for 'revision' property (create new revisions) should
+  // be respected for nodes. This requires some special treatment.
+  if ($context['entity_type'] == 'node' && in_array('revision', variable_get('node_options_' . $bundle_name)) && !in_array('revision', $context['selected']['properties'])) {
+    $wrapper->revision->set(1);
+  }
+
   if (!empty($context['selected']['properties'])) {
-    // Use the wrapper to set property values, since some properties need
-    // additional massaging by their setter callbacks.
-    // The wrapper will automatically modify $entity itself.
-    $wrapper = entity_metadata_wrapper($context['entity_type'], $entity);
     foreach ($context['selected']['properties'] as $key) {
       if (!$wrapper->$key->access('update')) {
         // No access.
@@ -113,7 +127,8 @@ function views_bulk_operations_modify_action($entity, $context) {
  * entity bundle, as provided by field_attach_form().
  */
 function views_bulk_operations_modify_action_form($context, &$form_state) {
-  // This action form uses admin-provided settings. If they were not set, pull the defaults now.
+  // This action form uses admin-provided settings. If they were not set, pull
+  // the defaults now.
   if (!isset($context['settings'])) {
     $context['settings'] = views_bulk_operations_modify_action_views_bulk_operations_form_options();
   }
@@ -126,7 +141,8 @@ function views_bulk_operations_modify_action_form($context, &$form_state) {
   // and filled with form data.
   // After submit, the pseudo-entities get passed to the actual action
   // (views_bulk_operations_modify_action()) which copies the data from the
-  // relevant pseudo-entity constructed here to the actual entity being modified.
+  // relevant pseudo-entity constructed here to the actual entity being
+  // modified.
   $form_state['entities'] = array();
 
   $info = entity_get_info($entity_type);
@@ -151,7 +167,16 @@ function views_bulk_operations_modify_action_form($context, &$form_state) {
         '#title' => $property['label'],
       );
 
-      $determined_type = ($property['type'] == 'boolean') ? 'checkbox' : 'textfield';
+      // According to _views_bulk_operations_modify_action_get_properties
+      // we have fixed list of supported types. Most of these types are string
+      // and only some of them has options list.
+      if (isset($property['options list'])) {
+        $determined_type = ($property['type'] == 'list') ? 'checkboxes' : 'select';
+      }
+      else {
+        $determined_type = ($property['type'] == 'boolean') ? 'checkbox' : 'textfield';
+      }
+
       $form['properties'][$key] = array(
         '#type' => $determined_type,
         '#title' => $property['label'],
@@ -189,7 +214,7 @@ function views_bulk_operations_modify_action_form($context, &$form_state) {
     }
   }
 
-  // Going to need this for multilingual nodes
+  // Going to need this for multilingual nodes.
   global $language;
   foreach ($bundles as $bundle_name => $bundle) {
     $bundle_key = $info['entity keys']['bundle'];
@@ -202,8 +227,8 @@ function views_bulk_operations_modify_action_form($context, &$form_state) {
     $entity = entity_create($context['entity_type'], $default_values);
     $form_state['entities'][$bundle_name] = $entity;
 
-    // Show the more detailed label only if the entity type has multiple bundles.
-    // Otherwise, it would just be confusing.
+    // Show the more detailed label only if the entity type has multiple
+    // bundles. Otherwise, it would just be confusing.
     if (count($info['bundles']) > 1) {
       $label = t('Fields for @bundle_key @label', array('@bundle_key' => $bundle_key, '@label' => $bundle['label']));
     }
@@ -417,9 +442,9 @@ function views_bulk_operations_modify_action_submit($form, $form_state) {
  * Properties that can't be changed are entity keys, timestamps, and the ones
  * without a setter callback.
  *
- * @param $entity_type
+ * @param string $entity_type
  *   The entity type whose properties will be fetched.
- * @param $display_values
+ * @param array $display_values
  *   An optional, admin-provided list of properties and fields that should be
  *   displayed for editing, used to filter the returned list of properties.
  */
@@ -435,8 +460,17 @@ function _views_bulk_operations_modify_action_get_properties($entity_type, $disp
     }
   }
   // List of supported types.
-  $supported_types = array('text', 'token', 'integer', 'decimal', 'date', 'duration',
-                           'boolean', 'uri', 'list');
+  $supported_types = array(
+    'text',
+    'token',
+    'integer',
+    'decimal',
+    'date',
+    'duration',
+    'boolean',
+    'uri',
+    'list',
+  );
   $property_info = entity_get_property_info($entity_type);
   if (empty($property_info['properties'])) {
     // Stop here if no properties were found.
@@ -484,9 +518,9 @@ function _views_bulk_operations_modify_action_get_properties($entity_type, $disp
  * (through the action settings) then only bundles that have at least one field
  * selected are returned.
  *
- * @param $entity_type
+ * @param string $entity_type
  *   The entity type whose bundles will be fetched.
- * @param $context
+ * @param array $context
  *   The VBO context variable.
  */
 function _views_bulk_operations_modify_action_get_bundles($entity_type, $context) {
@@ -594,8 +628,8 @@ function views_bulk_operations_modify_action_views_bulk_operations_form($options
   }
   foreach ($info['bundles'] as $bundle_name => $bundle) {
     $bundle_key = $info['entity keys']['bundle'];
-    // Show the more detailed label only if the entity type has multiple bundles.
-    // Otherwise, it would just be confusing.
+    // Show the more detailed label only if the entity type has multiple
+    // bundles. Otherwise, it would just be confusing.
     if (count($info['bundles']) > 1) {
       $label = t('Fields for @bundle_key @label', array('@bundle_key' => $bundle_key, '@label' => $bundle['label']));
     }

+ 23 - 11
sites/all/modules/contrib/views/views_bulk_operations/actions/user_cancel.action.inc

@@ -1,17 +1,19 @@
 <?php
 /**
-  * @file
-  * VBO action to cancel user accounts.
-  */
+ * @file
+ * VBO action to cancel user accounts.
+ */
 
 function views_bulk_operations_user_cancel_action_info() {
-  return array('views_bulk_operations_user_cancel_action' => array(
-    'type' => 'user',
-    'label' => t('Cancel user account'),
-    'configurable' => TRUE,
-    'behavior' => array('deletes_property'),
-    'triggers' => array('any'),
-  ));
+  return array(
+    'views_bulk_operations_user_cancel_action' => array(
+      'type' => 'user',
+      'label' => t('Cancel user account'),
+      'configurable' => TRUE,
+      'behavior' => array('deletes_property'),
+      'triggers' => array('any'),
+    ),
+  );
 }
 
 function views_bulk_operations_user_cancel_action_form($context) {
@@ -75,7 +77,17 @@ function views_bulk_operations_user_cancel_action($account, $context) {
       if (!empty($context['user_cancel_notify'])) {
         _user_mail_notify('status_canceled', $account);
       }
-      user_delete($account->uid);
+      // In cases when nodes are to be reassigned to UID 0, the user_delete must
+      // not run until *after* the user_cancel has been invoked, otherwise the
+      // nodes are deleted before they can be reassigned. Adding the user delete
+      // to the batch queue ensures things happen in the correct sequence.
+      $batch = array(
+        'operations' => array(
+          array('user_delete', array($account->uid)),
+        ),
+        'file' => drupal_get_path('module', 'node') . '/node.admin.inc',
+      );
+      batch_set($batch);
       watchdog('user', 'Deleted user: %name %email.', array('%name' => $account->name, '%email' => '<' . $account->mail . '>'), WATCHDOG_NOTICE);
       break;
   }

+ 3 - 3
sites/all/modules/contrib/views/views_bulk_operations/actions_permissions.info

@@ -3,9 +3,9 @@ description = Provides permission-based access control for actions. Used by View
 package = Administration
 core = 7.x
 
-; Information added by Drupal.org packaging script on 2015-07-01
-version = "7.x-3.3"
+; Information added by Drupal.org packaging script on 2017-02-21
+version = "7.x-3.4"
 core = "7.x"
 project = "views_bulk_operations"
-datestamp = "1435764542"
+datestamp = "1487698687"
 

+ 8 - 6
sites/all/modules/contrib/views/views_bulk_operations/css/views_bulk_operations.css

@@ -1,4 +1,5 @@
-.vbo-select-all-markup, .vbo-table-select-all-markup {
+.vbo-select-all-markup,
+.vbo-table-select-all-markup {
   display: none;
 }
 
@@ -15,9 +16,10 @@
 .views-table-row-select-all td {
   text-align: center;
 }
-.vbo-table-select-all-pages, .vbo-table-select-this-page {
-  margin: 0 !important;
-  padding: 2px 5px !important;
+.vbo-views-form .vbo-table-select-all-pages,
+.vbo-views-form .vbo-table-select-this-page {
+  margin: 0;
+  padding: 2px 5px;
 }
 
 /* Generic "select all" */
@@ -30,6 +32,6 @@
   margin-bottom: 0;
 }
 .vbo-fieldset-select-all div {
-  padding: 0 !important;
-  margin: 0 !important;
+  padding: 0;
+  margin: 0;
 }

+ 44 - 10
sites/all/modules/contrib/views/views_bulk_operations/js/views_bulk_operations.js

@@ -1,9 +1,17 @@
 (function ($) {
+  // Polyfill for jQuery less than 1.6.
+  if (typeof $.fn.prop != 'function') {
+    jQuery.fn.extend({
+      prop: jQuery.fn.attr
+    });
+  }
+
   Drupal.behaviors.vbo = {
     attach: function(context) {
       $('.vbo-views-form', context).each(function() {
         Drupal.vbo.initTableBehaviors(this);
         Drupal.vbo.initGenericBehaviors(this);
+        Drupal.vbo.toggleButtonsState(this);
       });
     }
   }
@@ -32,7 +40,8 @@
     // This is the "select all" checkbox in (each) table header.
     $('.vbo-table-select-all', form).click(function() {
       var table = $(this).closest('table')[0];
-      $('input[id^="edit-views-bulk-operations"]:not(:disabled)', table).attr('checked', this.checked);
+      $('input[id^="edit-views-bulk-operations"]:not(:disabled)', table).prop('checked', this.checked);
+      Drupal.vbo.toggleButtonsState(form);
 
       // Toggle the visibility of the "select all" row (if any).
       if (this.checked) {
@@ -83,35 +92,43 @@
     $('.vbo-select-all-markup', form).show();
 
     $('.vbo-select-this-page', form).click(function() {
-      $('input[id^="edit-views-bulk-operations"]', form).attr('checked', this.checked);
-      $('.vbo-select-all-pages', form).attr('checked', false);
+      $('input[id^="edit-views-bulk-operations"]', form).prop('checked', this.checked);
+      Drupal.vbo.toggleButtonsState(form);
+      $('.vbo-select-all-pages', form).prop('checked', false);
 
       // Toggle the "select all" checkbox in grouped tables (if any).
-      $('.vbo-table-select-all', form).attr('checked', this.checked);
+      $('.vbo-table-select-all', form).prop('checked', this.checked);
     });
     $('.vbo-select-all-pages', form).click(function() {
-      $('input[id^="edit-views-bulk-operations"]', form).attr('checked', this.checked);
-      $('.vbo-select-this-page', form).attr('checked', false);
+      $('input[id^="edit-views-bulk-operations"]', form).prop('checked', this.checked);
+      Drupal.vbo.toggleButtonsState(form);
+      $('.vbo-select-this-page', form).prop('checked', false);
 
       // Toggle the "select all" checkbox in grouped tables (if any).
-      $('.vbo-table-select-all', form).attr('checked', this.checked);
+      $('.vbo-table-select-all', form).prop('checked', this.checked);
 
       // Modify the value of the hidden form field.
       $('.select-all-rows', form).val(this.checked);
     });
 
+    // Toggle submit buttons' "disabled" states with the state of the operation
+    // selectbox.
+    $('select[name="operation"]', form).change(function () {
+      Drupal.vbo.toggleButtonsState(form);
+    });
+
     $('.vbo-select', form).click(function() {
       // If a checkbox was deselected, uncheck any "select all" checkboxes.
       if (!this.checked) {
-        $('.vbo-select-this-page', form).attr('checked', false);
-        $('.vbo-select-all-pages', form).attr('checked', false);
+        $('.vbo-select-this-page', form).prop('checked', false);
+        $('.vbo-select-all-pages', form).prop('checked', false);
         // Modify the value of the hidden form field.
         $('.select-all-rows', form).val('0')
 
         var table = $(this).closest('table')[0];
         if (table) {
           // Uncheck the "select all" checkbox in the table header.
-          $('.vbo-table-select-all', table).attr('checked', false);
+          $('.vbo-table-select-all', table).prop('checked', false);
 
           // If there's a "select all" row, hide it.
           if ($('.vbo-table-select-this-page', table).length) {
@@ -121,7 +138,24 @@
           }
         }
       }
+
+      Drupal.vbo.toggleButtonsState(form);
     });
   }
 
+  Drupal.vbo.toggleButtonsState = function(form) {
+    // If no rows are checked, disable any form submit actions.
+    var selectbox = $('select[name="operation"]', form);
+    var checkedCheckboxes = $('.vbo-select:checked', form);
+    var buttons = $('[id^="edit-select"] input[type="submit"]', form);
+
+    if (selectbox.length) {
+      var has_selection = checkedCheckboxes.length && selectbox.val() !== '0';
+      buttons.prop('disabled', !has_selection);
+    }
+    else {
+      buttons.prop('disabled', !checkedCheckboxes.length);
+    }
+  };
+
 })(jQuery);

+ 2 - 1
sites/all/modules/contrib/views/views_bulk_operations/views/views_bulk_operations.views.inc

@@ -6,7 +6,8 @@
 function views_bulk_operations_views_data_alter(&$data) {
   foreach (entity_get_info() as $entity_type => $info) {
     if (isset($info['base table']) && isset($data[$info['base table']]['table'])) {
-      $data[$info['base table']]['views_bulk_operations'] = array(
+      $data[$info['base table']]['views_bulk_operations']['moved to'] = array('views_entity_' . $entity_type, 'views_bulk_operations');
+      $data['views_entity_' . $entity_type]['views_bulk_operations'] = array(
         'title' => $data[$info['base table']]['table']['group'],
         'group' => t('Bulk operations'),
         'help' => t('Provide a checkbox to select the row for bulk operations.'),

+ 26 - 30
sites/all/modules/contrib/views/views_bulk_operations/views/views_bulk_operations_handler_field_operations.inc

@@ -6,7 +6,7 @@
 * Implements the Views Form API.
 */
 
-class views_bulk_operations_handler_field_operations extends views_handler_field {
+class views_bulk_operations_handler_field_operations extends views_handler_field_entity {
   var $revision = FALSE;
 
   function init(&$view, &$options) {
@@ -46,6 +46,12 @@ class views_bulk_operations_handler_field_operations extends views_handler_field
         unset($operation_options['use_queue']);
       }
     }
+
+    // Check whether this is a revision.
+    $table_data = views_fetch_data($this->table);
+    if (!empty($table_data['table']['revision'])) {
+      $this->revision = TRUE;
+    }
   }
 
   function option_definition() {
@@ -186,6 +192,14 @@ class views_bulk_operations_handler_field_operations extends views_handler_field
           $dom_id . '-selected' => array(1),
         ),
       );
+      $form['vbo_operations'][$operation_id]['skip_permission_check'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Skip permission step'),
+        '#default_value' => !empty($operation_options['skip_permission_check']),
+        '#dependency' => array(
+          $dom_id . '-selected' => array(1),
+        ),
+      );
 
       $form['vbo_operations'][$operation_id] += $operation->adminOptionsForm($dom_id, $this);
     }
@@ -263,19 +277,20 @@ class views_bulk_operations_handler_field_operations extends views_handler_field
     // At this point, the query has already been run, so we can access the results
     // in order to get the base key value (for example, nid for nodes).
     foreach ($this->view->result as $row_index => $row) {
-      $entity_id = $this->get_value($row);
+      $this->view->row_index = $row_index;
+      $id = $this->get_value($row, $this->real_field);
 
       if ($this->options['vbo_settings']['force_single']) {
         $form[$this->options['id']][$row_index] = array(
           '#type' => 'radio',
           '#parents' => array($this->options['id']),
-          '#return_value' => $entity_id,
+          '#return_value' => $id,
         );
       }
       else {
         $form[$this->options['id']][$row_index] = array(
           '#type' => 'checkbox',
-          '#return_value' => $entity_id,
+          '#return_value' => $id,
           '#default_value' => FALSE,
           '#attributes' => array('class' => array('vbo-select')),
         );
@@ -293,9 +308,12 @@ class views_bulk_operations_handler_field_operations extends views_handler_field
         if (empty($options['selected'])) {
           continue;
         }
-
         $operation = views_bulk_operations_get_operation($operation_id, $entity_type, $options);
-        if (!$operation || !$operation->access($user)) {
+        if (!$operation) {
+          continue;
+        }
+        $skip_permission_check = $operation->getAdminOption('skip_permission_check', FALSE);
+        if (!$operation->access($user) && !$skip_permission_check) {
           continue;
         }
         $selected[$operation_id] = $operation;
@@ -318,29 +336,7 @@ class views_bulk_operations_handler_field_operations extends views_handler_field
    * the entity type that VBO is operating on.
    */
   public function get_entity_type() {
-    $base_table = $this->view->base_table;
-
-    // If the current field is under a relationship you can't be sure that the
-    // base table of the view is the base table of the current field.
-    // For example a field from a node author on a node view does have users as base table.
-    if (!empty($this->options['relationship']) && $this->options['relationship'] != 'none') {
-      $relationships = $this->view->display_handler->get_option('relationships');
-      $options = $relationships[$this->options['relationship']];
-      $data = views_fetch_data($options['table']);
-      $base_table = $data[$options['field']]['relationship']['base'];
-    }
-    // The base table is now known, use it to determine the entity type.
-    foreach (entity_get_info() as $entity_type => $info) {
-      if (isset($info['base table']) && $info['base table'] == $base_table) {
-        return $entity_type;
-      }
-      elseif (isset($info['revision table']) && $info['revision table'] == $base_table) {
-        $this->revision = TRUE;
-        return $entity_type;
-      }
-    }
-    // This should never happen.
-    _views_bulk_operations_report_error("Could not determine the entity type for VBO field on views base table %table", array('%table' => $base_table));
-    return FALSE;
+    return $this->entity_type;
   }
+
 }

+ 1 - 1
sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.drush.inc

@@ -154,7 +154,7 @@ function views_bulk_operations_drush_execute($vid = NULL, $operation_id = NULL)
   $current = 1;
   foreach ($view->result as $row_index => $result) {
     $rows[$row_index] = array(
-      'entity_id' => $vbo->get_value($result),
+      'entity_id' => $result->{$vbo->real_field},
       'views_row' => array(),
       'position' => array(
         'current' => $current++,

+ 4 - 4
sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.info

@@ -1,7 +1,7 @@
 name = Views Bulk Operations
 description = Provides a way of selecting multiple rows and applying operations to them.
 dependencies[] = entity
-dependencies[] = views
+dependencies[] = views (>=3.12)
 package = Views
 core = 7.x
 php = 5.2.9
@@ -9,9 +9,9 @@ php = 5.2.9
 files[] = plugins/operation_types/base.class.php
 files[] = views/views_bulk_operations_handler_field_operations.inc
 
-; Information added by Drupal.org packaging script on 2015-07-01
-version = "7.x-3.3"
+; Information added by Drupal.org packaging script on 2017-02-21
+version = "7.x-3.4"
 core = "7.x"
 project = "views_bulk_operations"
-datestamp = "1435764542"
+datestamp = "1487698687"
 

+ 35 - 11
sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.module

@@ -45,6 +45,7 @@ function views_bulk_operations_load_action_includes() {
     'archive.action',
     'argument_selector.action',
     'book.action',
+    'change_owner.action',
     'delete.action',
     'modify.action',
     'script.action',
@@ -75,7 +76,7 @@ function views_bulk_operations_load_action_includes() {
  */
 function views_bulk_operations_cron() {
   db_delete('queue')
-    ->condition('name', db_like('views_bulk_operations_active_queue_'), 'LIKE')
+    ->condition('name', db_like('views_bulk_operations_active_queue_') . '%', 'LIKE')
     ->condition('created', REQUEST_TIME - 86400, '<')
     ->execute();
 }
@@ -214,7 +215,12 @@ function views_bulk_operations_get_operation_info($operation_id = NULL) {
 function views_bulk_operations_get_operation($operation_id, $entity_type, $options) {
   $operations = &drupal_static(__FUNCTION__);
 
-  if (!isset($operations[$operation_id])) {
+  // Create a unique hash of the options.
+  $cid = md5(serialize($options));
+
+  // See if there's a cached copy of the operation, including entity type and
+  // options.
+  if (!isset($operations[$operation_id][$entity_type][$cid])) {
     // Intentionally not using views_bulk_operations_get_operation_info() here
     // since it's an expensive function that loads all the operations on the
     // system, despite the fact that we might only need a few.
@@ -223,14 +229,14 @@ function views_bulk_operations_get_operation($operation_id, $entity_type, $optio
     $operation_info = $plugin['list callback']($operation_id);
 
     if ($operation_info) {
-      $operations[$operation_id] = new $plugin['handler']['class']($operation_id, $entity_type, $operation_info, $options);
+      $operations[$operation_id][$entity_type][$cid] = new $plugin['handler']['class']($operation_id, $entity_type, $operation_info, $options);
     }
     else {
-      $operations[$operation_id] = FALSE;
+      $operations[$operation_id][$entity_type][$cid] = FALSE;
     }
   }
 
-  return $operations[$operation_id];
+  return $operations[$operation_id][$entity_type][$cid];
 }
 
 /**
@@ -638,11 +644,11 @@ function theme_views_bulk_operations_confirmation($variables) {
   // All rows on all pages have been selected, so show a count of additional items.
   if ($select_all_pages) {
     $more_count = $vbo->view->total_rows - count($vbo->view->result);
-    $items[] = t('...and <strong>!count</strong> more.', array('!count' => $more_count));
+    $items[] = t('...and %count more.', array('%count' => $more_count));
   }
 
   $count = format_plural(count($entities), 'item', '@count items');
-  $output = theme('item_list', array('items' => $items, 'title' => t('You selected the following <strong>!count</strong>:', array('!count' => $count))));
+  $output = theme('item_list', array('items' => $items, 'title' => t('You selected the following %count:', array('%count' => $count))));
   return $output;
 }
 
@@ -902,10 +908,16 @@ function views_bulk_operations_adjust_selection($queue_name, $operation, $option
   }
 
   $vbo = _views_bulk_operations_get_field($view);
+
+  // Call views_handler_field_entity::pre_render() to get the entities.
+  $vbo->pre_render($view->result);
+
   $rows = array();
   foreach ($view->result as $row_index => $result) {
+    // Set the row index.
+    $view->row_index = $row_index;
     $rows[$row_index] = array(
-      'entity_id' => $vbo->get_value($result),
+      'entity_id' => $vbo->get_value($result, $vbo->real_field),
       'views_row' => array(),
       'position' => array(
         'current' => ++$context['sandbox']['progress'],
@@ -1075,7 +1087,8 @@ function views_bulk_operations_queue_item_process($queue_item_data, &$log = NULL
     }
 
     // If the current entity can't be accessed, skip it and log a notice.
-    if (!_views_bulk_operations_entity_access($operation, $entity_type, $entity, $account)) {
+    $skip_permission_check = $operation->getAdminOption('skip_permission_check');
+    if (!$skip_permission_check && !_views_bulk_operations_entity_access($operation, $entity_type, $entity, $account)) {
       $message = 'Skipped %operation on @type %title due to insufficient permissions.';
       $arguments = array(
         '%operation' => $operation->label(),
@@ -1127,12 +1140,22 @@ function views_bulk_operations_direct_adjust(&$selection, $vbo) {
     if ($field_name != $vbo->options['id']) {
       unset($view->field[$field_name]);
     }
+    else {
+      // Get hold of the new VBO field.
+      $new_vbo = $view->field[$field_name];
+    }
   }
 
   $view->execute($vbo->view->current_display);
+
+  // Call views_handler_field_entity::pre_render() to get the entities.
+  $new_vbo->pre_render($view->result);
+
   $results = array();
   foreach ($view->result as $row_index => $result) {
-    $results[$row_index] = $vbo->get_value($result);
+    // Set the row index.
+    $view->row_index = $row_index;
+    $results[$row_index] = $new_vbo->get_value($result, $new_vbo->real_field);
   }
   $selection = $results;
 }
@@ -1160,9 +1183,10 @@ function views_bulk_operations_direct_process($operation, $rows, $options) {
     }
     $entities = _views_bulk_operations_entity_load($entity_type, $entity_ids, $options['revision']);
 
+    $skip_permission_check = $operation->getAdminOption('skip_permission_check');
     // Filter out entities that can't be accessed.
     foreach ($entities as $id => $entity) {
-      if (!_views_bulk_operations_entity_access($operation, $entity_type, $entity)) {
+      if (!$skip_permission_check && !_views_bulk_operations_entity_access($operation, $entity_type, $entity, $account)) {
         $context['results']['log'][] = t('Skipped %operation on @type %title due to insufficient permissions.', array(
           '%operation' => $operation->label(),
           '@type' => $entity_type,

+ 15 - 5
sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.rules.inc

@@ -114,15 +114,25 @@ function views_bulk_operations_rules_action_info() {
 function views_bulk_operations_views_list() {
   $selectable_displays = array();
   foreach (views_get_enabled_views() as $name => $base_view) {
+    $view = $base_view->clone_view();
     foreach ($base_view->display as $display_name => $display) {
-      $view = $base_view->clone_view();
-      $view->build($display_name);
-      $vbo = _views_bulk_operations_get_field($view);
-      if ($vbo) {
-        $selectable_displays[$view->name . '|' . $display_name] = check_plain($view->human_name) . ' | ' . check_plain($display->display_title);
+      if (!$view->set_display($display_name)) {
+        continue;
+      }
+
+      // Initialize the style plugin and only continue to initialize handlers
+      // if the style uses fields.
+      if (!$view->init_style() || !$view->style_plugin->uses_fields()) {
+        continue;
+      }
+
+      $view->init_handlers($display_name);
+      if (_views_bulk_operations_get_field($view)) {
+        $selectable_displays[$view->name . '|' . $display_name] = check_plain($view->human_name . ' | ' . $display->display_title);
       }
     }
   }
+
   return $selectable_displays;
 }
 

BIN
sites/all/modules/contrib/views/views_data_export/images/csv.png


BIN
sites/all/modules/contrib/views/views_data_export/images/doc.png


BIN
sites/all/modules/contrib/views/views_data_export/images/txt.png


BIN
sites/all/modules/contrib/views/views_data_export/images/xls.png


BIN
sites/all/modules/contrib/views/views_data_export/images/xml.png


+ 93 - 28
sites/all/modules/contrib/views/views_data_export/plugins/views_data_export_plugin_display_export.inc

@@ -524,20 +524,30 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
           // TODO: Handle external databases.
           $result = db_query_range('SELECT * FROM {' . $this->index_tablename() . '} ORDER BY ' . $this->batched_execution_state->sandbox['weight_field_alias'] . ' ASC', 0, $this->get_option('segment_size'));
           $this->view->result = array();
-          foreach ($result as $item_hashed) {
-            $item = new stdClass();
-            // We had to shorten some of the column names in the index, restore
-            // those now.
-            foreach ($item_hashed as $hash => $value) {
-              if (isset($this->batched_execution_state->sandbox['field_aliases'][$hash])) {
-                $item->{$this->batched_execution_state->sandbox['field_aliases'][$hash]} = $value;
-              }
-              else {
-                $item->{$hash} = $value;
+          $query_plugin = get_class($this->view->query);
+          if ($query_plugin == 'views_plugin_query_default') {
+            foreach ($result as $item_hashed) {
+              $item = new stdClass();
+              // We had to shorten some of the column names in the index, restore
+              // those now.
+              foreach ($item_hashed as $hash => $value) {
+                if (isset($this->batched_execution_state->sandbox['field_aliases'][$hash])) {
+                  $item->{$this->batched_execution_state->sandbox['field_aliases'][$hash]} = $value;
+                }
+                else {
+                  $item->{$hash} = $value;
+                }
               }
+              // Push the restored $item in the views result array.
+              $this->view->result[] = $item;
+            }
+          }
+          elseif ($query_plugin == 'SearchApiViewsQuery') {
+            foreach ($result as $row) {
+              $item = unserialize($row->data);
+              $item->{$this->batched_execution_state->sandbox['weight_field_alias']} = $row->{$this->batched_execution_state->sandbox['weight_field_alias']};
+              $this->view->result[] = $item;
             }
-            // Push the restored $item in the views result array.
-            $this->view->result[] = $item;
           }
           $this->view->_post_execute();
           break;
@@ -623,25 +633,31 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
     // Get views to build the query.
     $view->build();
 
-    // Change the query object to use our custom one.
-    switch ($this->_get_database_driver()) {
-      case 'pgsql':
-        $query_class = 'views_data_export_plugin_query_pgsql_batched';
-        break;
+    $query_plugin = get_class($view->query);
+    if ($query_plugin == 'views_plugin_query_default') {
+      // Change the query object to use our custom one.
+      switch ($this->_get_database_driver()) {
+        case 'pgsql':
+          $query_class = 'views_data_export_plugin_query_pgsql_batched';
+          break;
 
-      default:
-        $query_class = 'views_data_export_plugin_query_default_batched';
-        break;
+        default:
+          $query_class = 'views_data_export_plugin_query_default_batched';
+          break;
+      }
+      $query = new $query_class();
+      // Copy the query over:
+      foreach ($view->query as $property => $value) {
+        $query->$property = $value;
+      }
+      // Replace the query object.
+      $view->query = $query;
+
+      $view->execute();
     }
-    $query = new $query_class();
-    // Copy the query over:
-    foreach ($view->query as $property => $value) {
-      $query->$property = $value;
+    elseif ($query_plugin == 'SearchApiViewsQuery') {
+      $this->store_search_api_result(clone($view));
     }
-    // Replace the query object.
-    $view->query = $query;
-
-    $view->execute();
   }
 
   /**
@@ -846,6 +862,55 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
     $conn_info = Database::getConnectionInfo($name);
     return $conn_info['default']['driver'];
   }
+
+  /**
+   * Based on views_data_export_plugin_query_default_batched::execute().
+   */
+  function store_search_api_result($view) {
+    $display_handler = &$view->display_handler;
+    $start = microtime(TRUE);
+
+    try {
+      // Get all the view results.
+      $view->query->set_limit(NULL);
+      $view->query->set_offset(0);
+      $view->query->execute($view);
+      $weight_alias = 'vde_weight';
+      $display_handler->batched_execution_state->sandbox['weight_field_alias'] = $weight_alias;
+
+      $schema = array(
+        'fields' => array(
+          $weight_alias => array('type' => 'int'),
+          'data' => array('type' => 'blob'),
+      ));
+
+      db_create_table($display_handler->index_tablename(), $schema);
+
+      if (!empty($view->result)) {
+        $insert_query = db_insert($display_handler->index_tablename())->fields(array($weight_alias, 'data'));
+        $weight = 0;
+        foreach ($view->result as $item) {
+          $insert_query->values(array(
+            $weight_alias => $weight,
+            'data' => serialize($item),
+          ));
+          $weight++;
+        }
+        $insert_query->execute();
+      }
+
+      $view->result = array();
+      // Now create an index for the weight field, otherwise the queries on the
+      // index will take a long time to execute.
+      db_add_unique_key($display_handler->index_tablename(), $weight_alias, array($weight_alias));
+    }
+    catch (Exception $e) {
+      $view->result = array();
+      debug('Exception: ' . $e->getMessage());
+    }
+
+    $view->execute_time = microtime(TRUE) - $start;
+  }
 }
 
 class views_data_export_plugin_query_default_batched extends views_plugin_query_default {

+ 3 - 1
sites/all/modules/contrib/views/views_data_export/views_data_export.drush.inc

@@ -169,7 +169,9 @@ function drush_views_data_export($view_name, $display_id, $output_file) {
   }
 
   $arguments = drush_get_option('arguments', '');
-  $arguments = explode(',', $arguments);
+  if(!empty($arguments) && is_array($arguments)) {
+    $arguments = explode(',', $arguments);
+  }
 
   if ($view->display_handler->is_batched()) {
     // This is a batched export, and needs to be handled as such.

+ 3 - 3
sites/all/modules/contrib/views/views_data_export/views_data_export.info

@@ -22,9 +22,9 @@ files[] = "tests/txt_export.test"
 files[] = "tests/xls_export.test"
 files[] = "tests/xml_export.test"
 
-; Information added by Drupal.org packaging script on 2016-09-20
-version = "7.x-3.1"
+; Information added by Drupal.org packaging script on 2017-04-05
+version = "7.x-3.2"
 core = "7.x"
 project = "views_data_export"
-datestamp = "1474360174"
+datestamp = "1491379387"
 

+ 6 - 1
sites/all/modules/contrib/views/views_data_export/views_data_export.install

@@ -185,7 +185,12 @@ function views_data_export_requirements($phase) {
       switch ($db_type) {
         case 'mysql':
           // Check the max allowed packet size.
-          $max_allowed_packet = db_query('SHOW VARIABLES WHERE variable_name = :name', array(':name' => 'max_allowed_packet'))->fetchField(1);
+          try {
+            $max_allowed_packet = db_query('SHOW VARIABLES WHERE variable_name = :name', array(':name' => 'max_allowed_packet'))->fetchField(1);
+          }
+          catch (Exception $e) {
+            $max_allowed_packet = NULL;
+          }
           if (is_numeric($max_allowed_packet)) {
             if ($max_allowed_packet < (16 * 1024 * 1024)) {
               $requirements['views_data_export'] = array(

+ 1 - 1
sites/all/modules/contrib/views/views_data_export/views_data_export.module

@@ -114,7 +114,7 @@ function views_data_export_cron() {
 function views_data_export_garbage_collect($expires = NULL, $chunk = NULL) {
   if (lock_acquire('views_data_export_gc')) {
     if (!isset($expires)) {
-      $expires = variable_get('views_data_export_gc_expires', 604800); // one week
+      $expires = variable_get('views_data_export_gc_expires', DRUPAL_MAXIMUM_TEMP_FILE_AGE);
     }
     if (!isset($chunk)) {
       $chunk = variable_get('views_data_export_gc_chunk', 30);

+ 43 - 0
sites/all/modules/contrib/views/views_send/CHANGELOG.txt

@@ -0,0 +1,43 @@
+Views Send 7.x-1.2, 2016-03-29
+------------------------------
+#2237585 by hansfn: Make allowed file extensions for attachments configurable
+#2368533 by hansfn: Bypassing views render layer breaks all sort of things (Also fixes #1833608 Views field rewriting)
+#2693393 by hansfn: Mailsystem 3.x-dev requires "key" and "module" keys in the mail message
+
+Views Send 7.x-1.1, 2014-06-21
+------------------------------
+#2225631 by hansfn: Using filter_fallback_format() instead of hard-coded value for message text format.
+#2258947 by hansfn: Call to a member function get_value() on a non-object.
+#2089409 by hansfn: Added support for Mandrill.
+
+Views Send 7.x-1.0, 2014-04-15
+------------------------------
+#2202769 by hansfn: Subject not displaying correctly
+#2122909 by hansfn: Unwanted conversion of quotes in fields used for recipient's e-mail and name
+#2023977 by hansfn: Views Send overrides Mail System module configuration
+#1963960 by hansfn: Add hooks.
+#1957442 by hansfn: Token replacement doesn't work for repeated field.
+#1939332 by hansfn: Token list doesn't update.
+#1937548 by hansfn: Use rendered fields.
+#1255194 by hansfn: Support multiple value email fields.
+#1051564 by hansfn: Support recipient & sender altering.
+#1894446 by hansfn: General token replacements doesn't work.
+#1571228 by hansfn: Fatal error when recipient's name not selected.
+#1497118 by hansfn: Queue processing with Batch API on D7.
+By hansfn: Improved Rules integration - email message exposed as tokens.
+#821530 by hansfn: Attachments to messages.
+#1210758 by Sanjo: Incorrect detection of PHP maximum execution time almost exceeded.
+#1513822 by hansfn: Rules integration.
+#1462126 by hansfn: Indicate which selected rows contain invalid e-mail addresses.
+#1477828 by bojanz: Stop relying on VBO, implement a custom Views Form handler instead.
+#1462118 by cbergmann, hansfn: Preview not working.
+#1454740 by hansfn: 'send mass mail' appears in VBO Operations drop-down even if not allowed.
+#1466638 by cbergmann: Always fetch the view from the context.
+#1468120 by cbergmann: views_send_debug has no effect.
+#1462118 by Ben Coleman, hansfn: Trim all email addresses.
+#1455820 by hansfn: Improve handling of formats
+#1451594 by hansfn: Make field support more robust for the recipient's name/e-mail
+#1449988 by hansfn: Fix token replacement for fields.
+#1411976 by julien66, hansfn: Added carbon copy (to sender).
+#1408756 by hansfn: Pass the selected views row(s) to the action.
+#1052040 by hansfn: Port to Drupal 7.

+ 339 - 0
sites/all/modules/contrib/views/views_send/LICENSE.txt

@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.

+ 65 - 0
sites/all/modules/contrib/views/views_send/README.txt

@@ -0,0 +1,65 @@
+
+Views Send allow mass mailing using Views.
+The messages are queued in a spool table and delivered only on cron.
+You can control how many messages will be send per cron run.
+
+CONFIGURATION
+
+General settings can be configured at: Site Configuration > Views Send
+
+USAGE
+
+ 1. Create a view and add at least one column containing e-mail addresses.
+ 2. [Optional] Expose Views filters to let the user easily build list of
+    recipients using UI.
+ 3. Add the "Global: Send e-mail" field to your view. This field provides the checkboxes
+    that allow the user to select multiple rows.
+ 4. Save the view, load the page, use exposed filters to build the list, select
+    all or some rows and choose "Send e-mail".
+ 5. Fill the message form to configure the e-mail. Use tokens to personalize
+    your e-mail.
+ 6. Preview and send the message.
+
+DEPENDENCIES & INTEGRATION
+
+ * Views Send depends on Views.
+ * The module integrates features from:
+  o Mime Mail. When the Mime Mail module is enabled, the user can choose to send
+    rich HTML messages and/or use attachments.
+  o Tokens. When the Tokens module is enabled, the user can insert context tokens
+    into the subject or body of the message. Note that row-based tokens are
+    available even if Tokens module is disabled.
+  o Rules. When the Rules module is enabled, the user can define actions 
+    for when emails are sent and/or placed in the spool.
+
+FOR DEVELOPERS / HOOKS
+
+The module provides two hooks: 
+ * hook_views_send_mail_queued($message, $view, $row_id)
+   Called just after each message is queued. 
+ * hook_views_send_mail_alter(&$message)
+   Called just before each message is queued.
+
+SIMILAR MODULES
+
+You may want to try also:
+
+ * Views Mail | http://drupal.org/project/views_mail
+   See what's different: http://drupal.org/node/795782
+ * Simplenews | http://drupal.org/project/simplenews
+   Some pieces of code were inspired by Simplenews module.
+
+HOW CAN YOU GET INVOLVED?
+
+ * Write a review for this module on http://drupalmodules.com/module/views-send
+ * Help translate this module at Drupal Localize Server
+   http://localize.drupal.org/translate/projects/views_send
+
+MAINTAINERS & SPONSORS
+
+ * Module maintainer
+   Hans Fredrik Nordhaug (hansfn) | http://drupal.org/user/40521
+ * Module author of original Drupal 6 version
+   Claudiu Cristea (claudiu.cristea) | http://drupal.org/user/56348
+ * The Drupal 6 version of this module was sponsored by Grafit SRL, 
+   now Webikon | http://www.webikon.com

+ 15 - 0
sites/all/modules/contrib/views/views_send/views/views_send.views.inc

@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * Implements hook_views_data_alter().
+ */
+function views_send_views_data_alter(&$data) {
+  $data['views']['views_send'] = array(
+    'title' => t('Send e-mail'),
+    'help' => t('Provide a checkbox to select the row for e-mail sending.'),
+    'field' => array(
+      'handler' => 'views_send_handler_field_selector',
+      'click sortable' => FALSE,
+    ),
+  );
+}

+ 60 - 0
sites/all/modules/contrib/views/views_send/views/views_send_handler_field_selector.inc

@@ -0,0 +1,60 @@
+<?php
+
+/**
+* @file
+* Views field handler. Outputs a checkbox for selecting a row for email sending.
+* Implements the Views Form API.
+*/
+
+class views_send_handler_field_selector extends views_handler_field {
+  /**
+   * If the view is using a table style, provide a
+   * placeholder for a "select all" checkbox.
+   */
+  function label() {
+    if ($this->view->style_plugin instanceof views_plugin_style_table) {
+      return '<!--views-send-select-all-->';
+    }
+    else {
+      return parent::label();
+    }
+  }
+
+  function query() {
+    // Do nothing.
+  }
+
+  function render($values) {
+    return '<!--form-item-' . $this->options['id'] . '--' . $this->view->row_index . '-->';
+  }
+
+  /**
+   * The form which replaces the placeholder from render().
+   */
+  function views_form(&$form, &$form_state) {
+    // The view is empty, abort.
+    if (empty($this->view->result)) {
+      return;
+    }
+
+    $form[$this->options['id']] = array(
+      '#tree' => TRUE,
+    );
+    foreach ($this->view->result as $row_index => $row) {
+      $form[$this->options['id']][$row_index] = array(
+        '#type' => 'checkbox',
+        '#default_value' => FALSE,
+        '#attributes' => array('class' => array('views-send-select')),
+      );
+    }
+  }
+
+  function views_form_validate($form, &$form_state) {
+    $field_name = $this->options['id'];
+    $selection = array_filter($form_state['values'][$field_name]);
+
+    if (empty($selection)) {
+      form_set_error($field_name, t('Please select at least one item.'));
+    }
+  }
+}

+ 50 - 0
sites/all/modules/contrib/views/views_send/views_send.admin.inc

@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ *   Views Send administration page.
+ *
+ * @ingroup views_send
+ */
+
+/**
+ * Callback for admin/settings/views_send menu item.
+ */
+function views_send_settings() {
+  $form = array();
+
+  if (VIEWS_SEND_MIMEMAIL) {
+    $form['views_send_attachment_valid_extensions'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Valid file extensions for attachments'),
+      '#default_value' => variable_get('views_send_attachment_valid_extensions', ''),
+      '#description' => t('A space separated list of allowed file extensions for attachments. Leave the list empty if you want to use the default list from file_save_upload().'),
+    );
+  }
+
+  $throttle = drupal_map_assoc(array(1, 10, 20, 30, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000));
+  $throttle[0] = t('Unlimited');
+
+  $form['views_send_throttle'] = array(
+    '#type' => 'select',
+    '#title' => t('Cron throttle'),
+    '#options' => $throttle,
+    '#default_value' => variable_get('views_send_throttle', 20),
+    '#description' => t('Sets the numbers of messages sent per cron run. Failure to send will also be counted. Cron execution must not exceed the PHP maximum execution time of %max seconds. You find the time spend to send e-mails in the !recent_logs.', array('%max' => ini_get('max_execution_time'), '!recent_logs' => l(t('Recent log entries'), 'admin/reports/dblog'))),
+  );
+  $form['views_send_spool_expire'] = array(
+    '#type' => 'select',
+    '#title' => t('Mail spool expiration'),
+    '#options' => array(0 => t('Immediate'), 1 => t('1 day'), 7 => t('1 week'), 14 => t('2 weeks')),
+    '#default_value' => variable_get('views_send_spool_expire', 0),
+    '#description' => t('E-mails are spooled. How long must messages be retained in the spool after successfull sending.'),
+  );
+  $form['views_send_debug'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Log e-mails'),
+    '#default_value' => variable_get('views_send_debug', FALSE),
+    '#description' => t('When checked all outgoing mesages are logged in the system log. A logged e-mail does not guarantee that it is send or will be delivered. It only indicates that a message is send to the PHP mail() function. No status information is available of delivery by the PHP mail() function.'),
+  );
+
+  return system_settings_form($form);
+}

+ 77 - 0
sites/all/modules/contrib/views/views_send/views_send.cron.inc

@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ *   Views Send cron rotuines.
+ *
+ * @ingroup views_send
+ */
+
+/**
+ * Process the spool queue at cron run.
+ */
+function views_send_send_from_spool() {
+  $limit = variable_get('views_send_throttle', 20);
+  $ok = $fail = $check = 0;
+
+  // Get PHP maximum execution time. 30 seconds default.
+  $max_execution_time = ini_get('max_execution_time') ? ini_get('max_execution_time') : VIEWS_SEND_MAX_EXECUTION_TIME;
+
+  // Reset a Drupal timer.
+  timer_start('views_send');
+
+  // Retrieve messages to be send.
+  $query = "SELECT * FROM {views_send_spool} WHERE status = :status ORDER BY tentatives ASC, timestamp ASC";
+  $result = $limit ? db_query_range($query, 0, $limit, array(':status' => 0)) : db_query($query, array(':status' => 0));
+  foreach ($result as $message) {
+    // Send the message.
+    $status = views_send_deliver($message);
+
+    if ($status) {
+      // Update the spool status.
+      db_query("UPDATE {views_send_spool} SET status = :status WHERE eid = :eid", array(':status' => 1, ':eid' => $message->eid));
+      if (variable_get('views_send_debug', FALSE)) {
+        watchdog('views_send', 'Message sent to %mail.', array('%mail' => $message->to_mail));
+      }
+      if (module_exists('rules')) {
+        rules_invoke_event('views_send_email_sent', $message);
+      }
+      $ok++;
+    }
+    else {
+      // Increment tentatives so that next time this message
+      // will be scheduled with low priority.
+      db_query("UPDATE {views_send_spool} SET tentatives = tentatives + 1 WHERE eid = :eid", array(':eid' => $message->eid));
+      $fail++;
+    }
+
+    // Check the elapsed time and break if we've spent more than 80%.
+    // We check every 50 messages.
+    if (++$check >= 50) {
+      // Reset the counter.
+      $check = 0;
+
+      // Break if exceded.
+      if (timer_read('views_send') / 1000 > .8 * $max_execution_time) {
+        watchdog('views_send', 'PHP maximum execution time almost exceeded. Remaining e-mail messages will be sent during the next cron run. If this warning occurs regularly you should reduce the cron throttle setting.', NULL, WATCHDOG_WARNING);
+        break;
+      }
+    }
+  }
+
+  if ($ok + $fail > 0) {
+    // Log results and exit.
+    watchdog('views_send', '%ok messages sent in %sec seconds, %fail failed sending.',
+      array('%ok' => $ok, '%sec' => timer_read('views_send') / 1000, '%fail' => $fail)
+    );
+  }
+}
+
+/**
+ * Clear the expired items from spool.
+ */
+function views_send_clear_spool() {
+  // TODO: Drupal 7: replace time() with REQUEST_TIME.
+  $expiration_time = time() - variable_get('views_send_spool_expire', 0) * 86400;
+  db_query("DELETE FROM {views_send_spool} WHERE status = :status AND timestamp <= :expiry", array(':status' => 1, 'expiry' => $expiration_time));
+}

+ 63 - 0
sites/all/modules/contrib/views/views_send/views_send.css

@@ -0,0 +1,63 @@
+.views-send-select-all-markup {
+  display: none;
+}
+
+/* "Select all" for table styles. */
+.views-send-selection-form thead .form-type-checkbox {
+  margin: 0;
+}
+.views-send-table-select-all {
+  display: none;
+}
+.views-table-row-select-all {
+  display: none;
+}
+.views-table-row-select-all td {
+  text-align: center;
+}
+.views-send-table-select-this-page {
+  margin: 0 !important;
+  padding: 2px 5px !important;
+}
+
+/* Generic "select all" */
+.views-send-fieldset-select-all {
+  text-align: center;
+  width: 210px;
+  padding: 0.6em 0;
+}
+.views-send-fieldset-select-all .form-item {
+  margin-bottom: 0;
+}
+.views-send-fieldset-select-all div {
+  padding: 0 !important;
+  margin: 0 !important;
+}
+
+
+form.views-send-preview label {
+  float: left;
+  clear: both;
+  padding: 2px;
+}
+
+form.views-send-preview .views-send-preview-value {
+  margin-left: 100px;
+  overflow: auto;
+  border: 1px solid #bbbbbb;
+  padding: 2px;
+}
+
+#views-send-preview-to {
+  height: 50px;
+}
+
+#views-send-preview-message {
+  height: 150px;
+  white-space:pre;
+}
+
+#views-send-preview-headers, #views-send-preview-attachments {
+  height: 80px;
+  font-family: monospace;
+}

+ 16 - 0
sites/all/modules/contrib/views/views_send/views_send.info

@@ -0,0 +1,16 @@
+name = "Views Send"
+description = "Provides a way to send e-mails from a list created with Views"
+dependencies[] = views
+configure = admin/config/system/views_send
+package = Views
+core = 7.x
+
+files[] = views_send.rules.inc
+files[] = views/views_send_handler_field_selector.inc
+
+; Information added by Drupal.org packaging script on 2017-04-03
+version = "7.x-1.6"
+core = "7.x"
+project = "views_send"
+datestamp = "1491222486"
+

+ 151 - 0
sites/all/modules/contrib/views/views_send/views_send.install

@@ -0,0 +1,151 @@
+<?php
+
+/**
+ * @file
+ *   The install and update code for the Views Send module.
+ *
+ * @ingroup views_send.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function views_send_schema() {
+  $schema['views_send_spool'] = array(
+    'description' => 'Table holds e-mails that are being send on cron.',
+    'fields' => array(
+      'eid' => array(
+        'description' => 'The primary identifier for an e-mail.',
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE),
+      'uid' => array(
+        'description' => 'The user that has sent the message.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0),
+      'timestamp' => array(
+        'description' => 'The Unix timestamp when the message was added to spool.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0),
+      'status' => array(
+        'description' => 'Status: 0 = pending; 1 = sent.',
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0),
+      'tentatives' => array(
+        'description' => 'How many times we tried to send this message.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'unsigned' => TRUE,
+        'default' => 0),
+      'from_name' => array(
+        'description' => 'The real name of the sender.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => FALSE,
+        'default' => ''),
+      'from_mail' => array(
+        'description' => 'The sender e-mail address.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'to_name' => array(
+        'description' => 'The real name of the recipient.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => FALSE,
+        'default' => ''),
+      'to_mail' => array(
+        'description' => 'The recipient e-mail address.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'subject' => array(
+        'description' => 'The e-mail subject.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => ''),
+      'body' => array(
+        'description' => 'The e-mail body.',
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',),
+      'headers' => array(
+        'description' => 'The e-mail additional headers.',
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',),
+    ),
+    'indexes' => array(
+      'uid' => array('uid'),
+      'timestamp' => array('timestamp'),
+    ),
+    'primary key' => array('eid'),
+  );
+  return $schema;
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function views_send_uninstall() {
+  db_query("DELETE FROM {variable} WHERE name LIKE 'views_send_%'");
+  db_query("DELETE FROM {cache} WHERE cid LIKE 'variables%'");
+}
+
+/**
+ * Remove unused variables.
+ */
+function views_send_update_6001() {
+  variable_del('views_send_format');
+  variable_del('views_send_from_mail');
+  variable_del('views_send_from_name');
+  variable_del('views_send_headers');
+  variable_del('views_send_message');
+  variable_del('views_send_priority');
+  variable_del('views_send_receipt');
+  variable_del('views_send_remember');
+  variable_del('views_send_subject');
+  variable_del('views_send_tokens');
+  variable_del('views_send_to_mail');
+  variable_del('views_send_to_name');
+  return array();
+}
+
+/**
+ * Remove views_send_format variables.
+ */
+function views_send_update_6002() {
+  $ret = array();
+  $ret[] = update_sql("DELETE FROM {variable} WHERE name LIKE 'views_send_format_%'");
+  return $ret;
+}
+
+/**
+ * Backend structure is altered for implementing http://drupal.org/node/808058
+ */
+function views_send_update_6003() {
+  $ret = array();
+
+  // Get updated schema.
+  $schema = views_send_schema();
+
+  // Rename body field from 'message' to 'body'.
+  db_change_field($ret, 'views_send_spool', 'message', 'body', $schema['views_send_spool']['fields']['body']);
+
+  // Drop other fields that are obsolete after spooling.
+  db_drop_field($ret, 'views_send_spool', 'format');
+  db_drop_field($ret, 'views_send_spool', 'priority');
+  db_drop_field($ret, 'views_send_spool', 'receipt');
+
+  return $ret;
+}

+ 53 - 0
sites/all/modules/contrib/views/views_send/views_send.js

@@ -0,0 +1,53 @@
+(function ($) {
+  // Polyfill for jQuery less than 1.6.
+  if (typeof $.fn.prop != 'function') {
+    jQuery.fn.extend({
+      prop: jQuery.fn.attr
+    });
+  }
+
+  Drupal.behaviors.viewsSend = {
+    attach: function(context) {
+      $('.views-send-selection-form', context).each(function() {
+        Drupal.viewsSend.initTableBehaviors(this);
+        Drupal.viewsSend.initGenericBehaviors(this);
+      });
+    }
+  }
+
+  Drupal.viewsSend = Drupal.viewsSend || {};
+  Drupal.viewsSend.initTableBehaviors = function(form) {
+    $('.views-send-table-select-all', form).show();
+    // This is the "select all" checkbox in (each) table header.
+    $('.views-send-table-select-all', form).click(function() {
+      var table = $(this).closest('table')[0];
+      $('input[id^="edit-views-send"]:not(:disabled)', table).prop('checked', this.checked).change();
+    });
+  }
+
+  Drupal.viewsSend.initGenericBehaviors = function(form) {
+    // Show the "select all" fieldset.
+    $('.views-send-select-all-markup', form).show();
+
+    $('.views-send-select-this-page', form).click(function() {
+      $('input[id^="edit-views-send"]', form).prop('checked', this.checked).change();
+
+      // Toggle the "select all" checkbox in grouped tables (if any).
+      $('.views-send-table-select-all', form).prop('checked', this.checked).change();
+    });
+
+    $('.views-send-select', form).click(function() {
+      // If a checkbox was deselected, uncheck any "select all" checkboxes.
+      if (!this.checked) {
+        $('.views-send-select-this-page', form).prop('checked', false).change();
+
+        var table = $(this).closest('table')[0];
+        if (table) {
+          // Uncheck the "select all" checkbox in the table header.
+          $('.views-send-table-select-all', table).prop('checked', false).change();
+        }
+      }
+    });
+  }
+
+})(jQuery);

+ 1325 - 0
sites/all/modules/contrib/views/views_send/views_send.module

@@ -0,0 +1,1325 @@
+<?php
+
+/**
+ * @file
+ *   The Views Send module.
+ *
+ * Views Send allow mass mailing using Views.
+ *
+ * @ingroup views_send
+ */
+
+/**
+ * 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);
+
+/**
+ * Capture PHP max_execution_time before drupal_cron_run().
+ * Workaround for Drupal 6.14. See http://drupal.org/node/584334
+ */
+define('VIEWS_SEND_MAX_EXECUTION_TIME', ini_get('max_execution_time'));
+
+/**
+ * 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 Mandrill).
+ */
+switch (true) {
+  case (module_exists('htmlmail') && module_exists('mailmime')):
+  case module_exists('mailgun'):
+  case module_exists('mandrill'):
+  case module_exists('mimemail'):
+  case module_exists('sendgrid_integration'):
+  case module_exists('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_selector($view) {
+  foreach ($view->field as $field_name => $field) {
+    if ($field instanceof views_send_handler_field_selector) {
+      // Add in the view object for convenience.
+      $field->view = $view;
+      return $field;
+    }
+  }
+  return FALSE;
+}
+
+/**
+ * Gets the field value from a result row in a view - rendered value (default),
+ * plain text or array with mail addresses.
+ * 
+ * @return
+ *  See description.
+ */
+function _views_send_get_field_value_from_views_row($view, $row_id, $field_id, $type='') {
+  if (strpos($field_id, 'custom_text') === 0) {
+    // Handle the special case for custom text fields.
+    $field_id = str_replace('custom_text', 'nothing', $field_id);
+  }
+  $rendered_field = $view->style_plugin->get_field($row_id, $field_id);
+
+  if ($type == 'plain_text') {
+    // Removing HTML tags. Used for names in headers, not body.
+    $result = strip_tags($rendered_field);
+  }
+  elseif ($type == 'mail') {
+    // Removing HTML tags and entities. Used for e-mail addresses in headers, not body.
+    $result = explode(',', decode_entities(strip_tags($rendered_field)));
+    $result = array_map('trim', $result);
+  }
+  else {
+    $result = $rendered_field;
+  }
+  return $result;
+}
+
+/**
+ * Implements hook_views_form_substitutions().
+ */
+function views_send_views_form_substitutions() {
+  // Views check_plains the column label, so do the same here in order for the
+  // replace operation to succeed.
+  $select_all_placeholder = check_plain('<!--views-send-select-all-->');
+  $select_all = array(
+    '#type' => 'checkbox',
+    '#default_value' => FALSE,
+    '#attributes' => array('class' => array('views-send-table-select-all')),
+  );
+  return array(
+    $select_all_placeholder => drupal_render($select_all),
+  );
+}
+
+
+/**
+ * Returns the 'select all' div that gets inserted above the view results
+ * for non-table style plugins.
+ *
+ * The actual insertion is done by JS, matching the degradation behavior
+ * of Drupal core (no JS - no select all).
+ */
+function theme_views_send_select_all($variables) {
+  $form = array();
+  $form['select_all'] = array(
+    '#type' => 'fieldset',
+    '#attributes' => array('class' => array('views-send-fieldset-select-all')),
+  );
+  $form['select_all']['this_page'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Select all items on this page'),
+    '#default_value' => '',
+    '#attributes' => array('class' => array('views-send-select-this-page')),
+  );
+
+  $output = '<div class="views-send-select-all-markup">';
+  $output .= drupal_render($form);
+  $output .= '</div>';
+
+  return $output;
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function views_send_form_alter(&$form, &$form_state, $form_id) {
+  if (strpos($form_id, 'views_form_') === 0) {
+    $field = _views_send_get_field_selector($form_state['build_info']['args'][0]);
+  }
+  // This form isn't used by Views Send.
+  if (empty($field)) {
+    return;
+  }
+
+  // Allow Views Send to work when embedded using views_embed_view(), or in a block.
+  if (empty($field->view->override_path)) {
+    if (!empty($field->view->preview) || $field->view->display_handler instanceof views_plugin_display_block) {
+      $field->view->override_path = $_GET['q'];
+    }
+  }
+  $query = drupal_get_query_parameters($_GET, array('q'));
+  $form['#action'] = url($field->view->get_url(), array('query' => $query));
+
+  // Cache the built form to prevent it from being rebuilt prior to validation
+  // and submission, which could lead to data being processed incorrectly,
+  // because the views rows (and thus, the form elements as well) have changed
+  // in the meantime. Matching views issue: http://drupal.org/node/1473276.
+  $form_state['cache'] = TRUE;
+
+  // Add the custom CSS for all steps of the form.
+  $form['#attached']['css'][] = drupal_get_path('module', 'views_send') . '/views_send.css';
+
+  if ($form_state['step'] == 'views_form_views_form') {
+    $form['actions']['submit']['#value'] = t('Send e-mail');
+    $form['actions']['submit']['#submit'] = array('views_send_form_submit');
+    if (isset($form['#prefix']) && ($form['#prefix'] == '<div class="vbo-views-form">')) {
+      $form['#prefix'] = '<div class="vbo-views-form views-send-selection-form">';
+    }
+    else {
+      $form['#prefix'] = '<div class="views-send-selection-form">';
+    }
+    $form['#suffix'] = '</div>';
+
+    // Add the custom JS for this step of the form.
+    $form['#attached']['js'][] = drupal_get_path('module', 'views_send') . '/views_send.js';
+
+    // Adds the "select all" functionality for non-table style plugins
+    // if the view has results.
+    if (!empty($field->view->result) && !($field->view->style_plugin instanceof views_plugin_style_table)) {
+      $form['select_all_markup'] = array(
+        '#type' => 'markup',
+        '#markup' => theme('views_send_select_all'),
+      );
+    }
+  }
+}
+
+/**
+ * Multistep form callback for the "configure" step.
+ *
+ @TODO: Hide "Sender" (from) if Mandrill is used.
+ */
+function views_send_config_form($form, &$form_state, $view, $output) {
+  if (!empty($form_state['configuration'])) {
+    // Values entered in the "config" step.
+    $config = $form_state['configuration'];
+  }
+  $display = $view->name . ':' . $view->current_display;
+  $form['display'] = array(
+    '#type' => 'value',
+    '#value' => $display,
+  );
+  $form['from'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Sender'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE,
+  );
+  $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['views_send_from_name']) ? $config['views_send_from_name'] : variable_get('views_send_from_name_' . $display, variable_get('site_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['views_send_from_mail']) ? $config['views_send_from_mail'] : variable_get('views_send_from_mail_' . $display, variable_get('site_mail', ini_get('sendmail_from'))),
+    '#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' => 'fieldset',
+    '#title' => t('Recipients'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE,
+  );
+  $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['views_send_to_name']) ? $config['views_send_to_name'] : variable_get('views_send_to_name_' . $display, ''),
+  );
+  $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['views_send_to_mail']) ? $config['views_send_to_mail'] : variable_get('views_send_to_mail_' . $display, ''),
+    '#required' => TRUE,
+  );
+  $form['mail'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('E-mail content'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE,
+  );
+  $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['views_send_subject']) ? $config['views_send_subject'] : variable_get('views_send_subject_' . $display, ''),
+  );
+  $form['mail']['views_send_message'] = array(
+    '#type' => 'text_format',
+    '#title' => t('Message'),
+    '#description' => t('Enter the body of the message. You can use tokens in the message.'),
+    '#required' => TRUE,
+    '#rows' => 10,
+  );
+  if (isset($config['views_send_message']['value'])) {
+    $form['mail']['views_send_message'] += array(
+      '#format' => $config['views_send_message']['format'],
+      '#default_value' => $config['views_send_message']['value'],
+    );
+  }
+  else {
+    $saved_message = variable_get('views_send_message_' . $display);
+    $form['mail']['views_send_message'] += array(
+      '#format' => isset($saved_message['format']) ? $saved_message['format'] : filter_fallback_format(),
+      '#default_value' => isset($saved_message['value']) ? $saved_message['value'] : '',
+    );
+  }
+  $form['mail']['token'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Tokens'),
+    '#description' => t('You can use the following tokens in the subject or message.'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+  if (!module_exists('token')) {
+    $form['mail']['token']['tokens'] = array(
+      '#markup' => theme('views_send_token_help', $fields_name_text)
+    );
+  }
+  else {
+    $form['mail']['token']['views_send'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Views Send specific tokens'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+    );
+    $form['mail']['token']['views_send']['tokens'] = array(
+      '#markup' => theme('views_send_token_help', $fields_name_text)
+    );
+    $form['mail']['token']['general'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('General tokens'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+    );
+    $token_types = array('site', 'user', 'node', 'current-date');
+    $form['mail']['token']['general']['tokens'] = array(
+      '#markup' => theme('token_tree', array('token_types' => $token_types))
+    );
+  }
+  if (VIEWS_SEND_MIMEMAIL && user_access('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' => 'fieldset',
+    '#title' => t('Additional e-mail options'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+  $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['views_send_priority']) ? $config['views_send_priority'] : variable_get('views_send_priority_' . $display, 0),
+  );
+  $form['additional']['views_send_receipt'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Request receipt'),
+    '#default_value' => isset($config['views_send_receipt']) ? $config['views_send_receipt'] : variable_get('views_send_receipt_' . $display, 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['views_send_headers']) ? $config['views_send_headers'] : variable_get('views_send_headers_' . $display, ''),
+  );
+
+  $form['views_send_direct'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Send the message directly using the Batch API.'),
+    '#default_value' => isset($config['views_send_direct']) ? $config['views_send_direct'] : variable_get('views_send_direct_'. $display, TRUE),
+  );
+  $form['views_send_carbon_copy'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Send a copy of the message to the sender.'),
+    '#default_value' => isset($config['views_send_carbon_copy']) ? $config['views_send_carbon_copy'] : variable_get('views_send_carbon_copy_' . $display, TRUE),
+  );
+
+  $form['views_send_remember'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Remember these values for the next time a mass mail is sent. (The values are not stored per user.)'),
+    '#default_value' => variable_get('views_send_remember_' . $display, FALSE),
+  );
+  $query = drupal_get_query_parameters($_GET, array('q'));
+  $form['actions'] = array(
+    '#type' => 'container',
+    '#attributes' => array('class' => array('form-actions')),
+    '#weight' => 999,
+  );
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Next'),
+    '#validate' => array('views_send_config_form_validate'),
+    '#submit' => array('views_send_form_submit'),
+    '#suffix' => l(t('Cancel'), $view->get_url(), array('query' => $query)),
+  );
+
+  return $form;
+}
+
+/**
+ * Validation callback for the "configure" step.
+ */
+function views_send_config_form_validate($form, &$form_state) {
+  $values =& $form_state['values'];
+  $view = $form_state['build_info']['args'][0];
+
+  $formats = filter_formats();
+  if (!filter_access($formats[$values['views_send_message']['format']])) {
+    form_set_error('views_send_message', t('Illegale format selected'));
+  }
+
+  // Check if sender's e-mail is a valid one.
+  if (!valid_email_address(trim($values['views_send_from_mail']))) {
+    form_set_error('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']];
+    foreach ($form_state['selection'] as $row_id) {
+      $mail_addresses = _views_send_get_field_value_from_views_row($view, $row_id, $to_mail_field, 'mail');
+      foreach ($mail_addresses as $mail_address) {
+        if (!valid_email_address($mail_address)) {
+          $wrong_addresses[$row_id] = $mail_address;
+          break;
+        }
+      }
+    }
+
+    if (count($wrong_addresses) > 0) {
+      if (count($wrong_addresses) == count($form_state['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($form_state['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, check_plain($wrong_address));
+        }
+        $error_message .= '</table>';
+      }
+      form_set_error('views_send_to_mail', $error_message);
+    }
+  }
+}
+
+/**
+ * Multistep form callback for the "confirm" step.
+ * Allows the user to preview the whole message before sending it.
+ */
+function views_send_confirm_form($form, &$form_state, $view, $output) {
+  drupal_set_title(t('Review and confirm the message that is about to be sent'));
+
+  // Values entered in the "config" step.
+  $configuration = $form_state['configuration'];
+
+  if (!VIEWS_SEND_MIMEMAIL && ($configuration['views_send_message']['format'] != 'plain_text')) {
+    drupal_set_message(
+      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 !mimemail or !mandrill module.",
+        array(
+          '!mimemail' => '<a href="http://drupal.org/project/mimemail">Mime Mail</a>',
+          '!mandrill' => '<a href="http://drupal.org/project/mandrill">Mandrill</a>'
+        )
+      )
+    );
+  }
+
+  // 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">' .
+        check_plain(_views_send_format_address($from_mail, $from_name, FALSE)) .
+      '</div>',
+  );
+
+  // To: parts. (Mail is mandatory, name is optional.)
+  $recipients = array();
+  if (!empty($configuration['views_send_to_name'])) {
+    $to_name_field = $configuration['views_send_tokens'][$configuration['views_send_to_name']];
+  }
+  else {
+    $to_name_field = false;
+    $to_name = '';
+  }
+  $to_mail_field = $configuration['views_send_tokens'][$configuration['views_send_to_mail']];
+  foreach ($form_state['selection'] as $row_id) {
+    if ($to_name_field) {
+      $to_name = _views_send_get_field_value_from_views_row($view, $row_id, $to_name_field, 'plain_text');
+    }
+    $mail_addresses = _views_send_get_field_value_from_views_row($view, $row_id, $to_mail_field, 'mail');
+    foreach ($mail_addresses as $mail_address) {
+      $recipients[] = check_plain(_views_send_format_address($mail_address, $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">' . check_plain($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[] = check_plain($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']) && user_access('attachments with views_send')) {
+    foreach ($configuration['views_send_attachments'] as $attachment) {
+      $attachments[] = check_plain($attachment['filename']);
+    }
+    $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 = drupal_get_query_parameters($_GET, array('q'));
+  $form['actions'] = array(
+    '#type' => 'container',
+    '#attributes' => array('class' => array('form-actions')),
+    '#weight' => 999,
+  );
+  $form['actions']['back'] = array(
+    '#type' => 'submit',
+    '#value' => t('Go back'),
+    '#submit' => array('views_send_form_back_submit'),
+  );
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Send'),
+    '#submit' => array('views_send_form_submit'),
+    '#suffix' => l(t('Cancel'), $view->get_url(), array('query' => $query)),
+  );
+
+  return $form;
+}
+
+/**
+ * Submit handler for all steps of the Views Send multistep form.
+ */
+function views_send_form_submit($form, &$form_state) {
+  $field = _views_send_get_field_selector($form_state['build_info']['args'][0]);
+
+  switch ($form_state['step']) {
+    case 'views_form_views_form':
+      $field_name = $field->options['id'];
+      $selection = array_filter($form_state['values'][$field_name]);
+      $form_state['selection'] = array_keys($selection);
+
+      $form_state['step'] = 'views_send_config_form';
+      $form_state['rebuild'] = TRUE;
+      break;
+
+    case 'views_send_config_form':
+      $display = $form['display']['#value'];
+      foreach ($form_state['values'] as $key => $value) {
+        $key = ($key == 'format') ? 'views_send_message_format' : $key;
+        if (substr($key, 0, 11) == 'views_send_') {
+          if ($form_state['values']['views_send_remember']) {
+            variable_set($key . '_' . $display, $value);
+          }
+          else {
+            variable_del($key . '_' . $display);
+          }
+        }
+      }
+      $form_state['configuration'] = $form_state['values'];
+      $form_state['configuration']['views_send_attachments'] = array();
+
+      // If a file was uploaded, process it.
+      if (VIEWS_SEND_MIMEMAIL && user_access('attachments with views_send') && isset($_FILES['files']) && is_uploaded_file($_FILES['files']['tmp_name']['views_send_attachments'])) {
+        // attempt to save the uploaded file
+        $dir = file_default_scheme() . '://views_send_attachments';
+        file_prepare_directory($dir, FILE_CREATE_DIRECTORY);
+        $file_extensions = variable_get('views_send_attachment_valid_extensions', FALSE);
+        if ($file_extensions) {
+          $file_validators['file_validate_extensions'] = array();
+          $file_validators['file_validate_extensions'][0] = $file_extensions;
+        }
+        else {
+          $file_validators = array();
+        }
+        $file = file_save_upload('views_send_attachments', $file_validators, $dir);
+        // set error if file was not uploaded
+        if (!$file) {
+          //form_set_error('views_send_attachment', 'Error uploading file.');
+        }
+        else {
+          // set files to form_state, to process when form is submitted
+          // @todo: when we add a multifile formfield then loop through to add each file to attachments array
+          $form_state['configuration']['views_send_attachments'][] = (array)$file;
+        }
+      }
+
+      $form_state['step'] = 'views_send_confirm_form';
+      $form_state['rebuild'] = TRUE;
+      break;
+
+    case 'views_send_confirm_form':
+      // Queue the email for sending.
+      views_send_queue_mail($form_state['configuration'], $form_state['selection'], $field->view);
+
+      // Redirect.
+      $query = drupal_get_query_parameters($_GET, array('q'));
+      $form_state['redirect'] = array($field->view->get_url(), array('query' => $query));
+      break;
+  }
+}
+
+/**
+ * Submit handler that handles back buttons.
+ */
+function views_send_form_back_submit($form, &$form_state) {
+  switch ($form_state['step']) {
+    case 'views_send_confirm_form':
+      $form_state['step'] = 'views_send_config_form';
+      $form_state['rebuild'] = TRUE;
+      break;
+  }
+}
+
+/**
+ * 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 object.
+ */
+function views_send_queue_mail($params, $selected_rows, $view) {
+  global $user;
+
+  if (!user_access('mass mailing with views_send')) {
+    drupal_set_message(
+      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('admin/people/permissions', array('fragment' => 'module-views_send')))),
+      'error'
+    );
+    return;
+  }
+
+  // From: parts.
+  $from_mail = trim($params['views_send_from_mail']);
+  $from_name = $params['views_send_from_name'];
+
+  // To: parts. (Mail is mandatory, name is optional.)
+  $to_mail_key = $params['views_send_tokens'][$params['views_send_to_mail']];
+  if (!empty($params['views_send_to_name'])) {
+    $to_name_key = $params['views_send_tokens'][$params['views_send_to_name']];
+  }
+  else {
+    $to_name_key = false;
+    $to_name = '';
+  }
+
+  $subject = $params['views_send_subject'];
+  $body = $params['views_send_message']['value'];
+  $headers = _views_send_headers($params['views_send_receipt'], $params['views_send_priority'], $from_mail, $params['views_send_headers']);
+  $format = $params['views_send_message']['format'];
+  $attachments = $params['views_send_attachments'];
+
+  $formats = filter_formats();
+  if (!filter_access($formats[$format])) {
+    drupal_set_message(t('No mails sent since an illegale format is selected for the message.'));
+    return;
+  }
+  else {
+    $body = check_markup($body, $format);
+  }
+
+  if ($format == 'plain_text') {
+    $plain_format = TRUE;
+  }
+  else {
+    $plain_format = FALSE;
+  }
+
+  $message_base = array(
+    'uid' => $user->uid,
+    'from_name' => trim($from_name),
+    'from_mail' => trim($from_mail),
+    'headers' => $headers,
+  );
+
+  foreach ($selected_rows as $selected_rows_key => $row_id) {
+    // To: parts.
+    $to_mail = implode(',', _views_send_get_field_value_from_views_row($view, $row_id, $to_mail_key, 'mail'));
+    if ($to_name_key) {
+      $to_name = _views_send_get_field_value_from_views_row($view, $row_id, $to_name_key, 'plain_text');
+    }
+
+    // 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[] = _views_send_get_field_value_from_views_row($view, $row_id, $field_name);
+    }
+
+    // Views Send specific token replacements
+    $subject_expanded = str_replace($token_keys, $token_values, $subject);
+    $body_expanded = 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'] = node_load($view->result[$row_id]->nid);
+    }
+    $subject_expanded = token_replace($subject_expanded, $data);
+    $body_expanded = token_replace($body_expanded, $data);
+
+    if (!VIEWS_SEND_MIMEMAIL || (variable_get('mimemail_format', 'plain_text') == 'plain_text')) {
+      $body_expanded = drupal_html_to_text($body_expanded);
+    }
+
+    $message = $message_base + array(
+      'timestamp' => time(),
+      'to_name' => trim($to_name),
+      'to_mail' => trim($to_mail),
+      'subject' => strip_tags($subject_expanded),
+      'body' => $body_expanded,
+    );
+
+    // Enable other modules to alter the actual message before queueing it
+    // by providing the hook 'views_send_mail_alter'.
+    drupal_alter('views_send_mail', $message);
+
+    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);
+      // Only queue the message if it hasn't been cancelled by another module.
+      if ($message['send']) {
+        unset($message['send']);
+        // Removing stuff add to work around Swifth Mailer issue 2841663
+        if (module_exists('swiftmailer')) {
+          unset($message['params']);
+        }
+
+        db_insert('views_send_spool')->fields($message)->execute();
+        if (module_exists('rules')) {
+          rules_invoke_event('views_send_email_added_to_spool', $message);
+        }
+
+        // Enabled other modules to act just after a message is queued
+        // by providing the hook 'views_send_mail_queued'.
+        module_invoke_all('views_send_mail_queued', $message, $view, $row_id);
+      }
+      else {
+        unset($selected_rows[$selected_rows_key]);
+      }
+    }
+  }
+
+  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));
+    }
+
+    $batch = array(
+      'operations' => $operations,
+      'finished' => 'views_send_batch_deliver_finished',
+      'progress_message' => t('Sent @current of @total messages.'),
+    );
+    batch_set($batch);
+    drupal_set_message(
+      format_plural(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;
+      db_insert('views_send_spool')->fields($message)->execute();
+    }
+
+    drupal_set_message(
+      format_plural(count($selected_rows), '1 message added to the spool.', '@count messages added to the spool.')
+    );
+    if (module_exists('rules')) {
+      rules_invoke_event('views_send_all_email_added_to_spool', count($selected_rows));
+    }
+  }
+
+}
+
+// === 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.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('views_send_settings'),
+    'access arguments' => array('administer views_send'),
+    'file' => 'views_send.admin.inc',
+  );
+  return $items;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function views_send_permission() {
+  $perms = array(
+    'administer views_send' => array(
+      'title' => t('Administer mass mail with Views'),
+      'description' => t('Configure sending of e-mails to a list created with Views.'),
+    ),
+    'mass mailing with views_send' => array(
+      'title' => t('Send mass mail with Views'),
+      'description' => t('Send e-mails to a list created with Views.'),
+    ),
+  );
+  if (VIEWS_SEND_MIMEMAIL) {
+    $perms['attachments with views_send'] = array(
+      'title' => t('Use attachments with Views Send'),
+      'description' => t('Attach files to e-mails sent with Views Send.'),
+    );
+  }
+  return $perms;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function views_send_theme($existing, $type, $theme, $path) {
+  return array(
+    'views_send_select_all' => array(
+      'variables' => array(),
+    ),
+    'views_send_token_help' => array(
+      'arguments' => array('tokens' => array()),
+    ),
+  );
+}
+
+/**
+ * Implements hook_views_api().
+ */
+function views_send_views_api() {
+  return array(
+    'api' => 3,
+    'path' => drupal_get_path('module', 'views_send') . '/views',
+  );
+}
+
+/**
+ * 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'];
+  }
+
+  // TODO: Implement node message parsing.
+  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 {
+    $name = ($encode ? _views_send_mime_header_encode($name) : $name);
+    return sprintf('"%s" <%s>', $name, $mail);
+  }
+}
+
+/**
+ * Returns a mime-encoded string for strings that contain UTF-8.
+ *
+ * Simplified and correct version of mime_header_decode.
+ */ 
+function _views_send_mime_header_encode($string) {
+  if (preg_match('/[^\x20-\x7E]/', $string)) {
+    $string = '=?UTF-8?B?' . base64_encode($string) . '?=';
+  }
+  return $string;
+}
+
+/**
+ * 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'] = $attachments;
+    if ($plain_format) {
+      $params['plain'] = TRUE;
+    }
+  }
+
+  // Call Drupal standard mail function, but without sending.
+  $mail = drupal_mail('views_send', $key, $params['to_formatted'], language_default(), $params, $params['from_formatted'], FALSE);
+
+  // Add additional Mime Mail post processing.
+  if (VIEWS_SEND_MIMEMAIL) {
+    // We want to spool the Subject decoded.
+    $mail['subject'] = mime_header_decode($mail['subject']);
+  }
+
+  // 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['send'] = $mail['send'];
+  $message['headers'] = serialize($mail['headers']);
+  // Working around a Swifth Mailer issue - see https://www.drupal.org/node/2841663
+  if (module_exists('swiftmailer')) {
+    $message['params'] = array('attachments' => $params['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(
+    'id' => 'views_send_' . $key,
+    'module' => 'views_send',
+    'key' => $key,
+    'to' => $message->to_mail,
+    'from' => $message->from_mail,
+    'subject' => $message->subject,
+    'body' => $message->body,
+    'headers' => $headers,
+  );
+  // Because of Swifth Mailer issue 2841663
+  if (module_exists('swiftmailer')) {
+    $mail['params'] = $message->params;
+  }
+
+  // Mime encode the subject before passing to the mail function 
+  // to work around a bug in Drupal's mime_header_encode.
+  $mail['subject'] = _views_send_mime_header_encode($message->subject);
+  
+  $system = drupal_mail_system('views_send', $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);
+  if (!$message['send']) {
+    $context['results'][] = t('Skipping sending message to %mail.',
+      array('%mail' => $message['to_mail']));
+    return;
+  }
+  else {
+    unset($message['send']);
+  }
+  
+  $status = views_send_deliver($message);
+
+  if ($status) {
+    if (variable_get('views_send_debug', FALSE)) {
+      watchdog('views_send', 'Message sent to %mail.', array('%mail' => $message['to_mail']));
+    }
+    if (module_exists('rules')) {
+      rules_invoke_event('views_send_email_sent', $message);
+    }
+  }
+  else {
+    $context['results'][] = t('Failed sending message to %mail - spooling it.',
+      array('%mail' => $message['to_mail']));
+    // Queue the message to the spool table.
+    db_insert('views_send_spool')->fields($message)->execute();
+    if (module_exists('rules')) {
+      rules_invoke_event('views_send_email_added_to_spool', $message);
+    }
+  }
+}
+
+/**
+ * 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_set_message($result);
+    }
+  }
+}
+
+// === Theming functions =======================================================
+
+/**
+ * Theme the replacement tokens.
+ *
+ * @param $tokens:
+ *   Keyed array with tokens as keys and description as values.
+ *
+ * @return
+ *   A themed table wirh all tokens.
+ *
+ * @todo: Add help for other tokens
+ */
+function theme_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);
+  }
+  $output = theme('table', array('header' => $header, 'rows' => $rows));
+  return $output;
+}
+
+/**
+ * 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();
+  foreach ($view->field as $field_name => $field) {
+    // Ignore Views Form fields.
+    if (property_exists($field, 'views_form_callback') || method_exists($field, 'views_form')) {
+      continue;
+    }
+    if ($field instanceof views_handler_field_custom) {
+      $field_key = $field_name;
+      // Using a nice field name (for tokens) for custom text fields.
+      $field_name = str_replace('nothing', 'custom_text', $field_name);;
+    } 
+    elseif (!empty($field->field_info)) {
+      $field_key = $field->field_info['field_name'];
+    }
+    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;
+}
+
+

+ 56 - 0
sites/all/modules/contrib/views/views_send/views_send.rules.inc

@@ -0,0 +1,56 @@
+<?php
+
+/**
+* Implementation of hook_rules_event_info().
+*/
+function views_send_rules_event_info() {
+  $defaults = array(
+    'group' => t('Views Send'),
+  );
+  return array(
+    'views_send_email_sent' => $defaults + array(
+      'label' => t('After sending an individual email'),
+      'variables' => array(
+        'views_send_email_message' => array(
+          'type' => 'views_send_email_message',
+          'label' => t('e-mail message'),
+          'description' => t('All information about the message.')
+        ),
+      ),
+    ),
+    'views_send_all_email_added_to_spool' => $defaults + array(
+      'label' => t('After adding all e-mails to the spool'),
+      'variables' => array(
+        'views_send_email_count' => array(
+          'type' => 'integer',
+          'label' => t('message count'),
+          'description' => t('The number of messages added to the spool.')
+        ),
+      ),
+    ),
+    'views_send_email_added_to_spool' => $defaults + array(
+      'label' => t('After adding an individual e-mail to the spool'),
+      'variables' => array(
+        'views_send_email_message' => array(
+          'type' => 'views_send_email_message',
+          'label' => t('e-mail message'),
+          'description' => t('All information about the message.')
+        ),
+      ),
+    ),
+  );
+}
+
+/**
+* Implementation of hook_rules_data_info().
+*/
+function views_send_rules_data_info() {
+  return array(
+    'views_send_email_message' => array(
+      'label' => t('Views Send e-mail message'), 
+      'group' => t('Views Send'), 
+      'wrap' => TRUE, 
+      'property info' => _views_send_email_message_property_info(),
+    ),
+  );
+}

+ 45 - 0
sites/all/modules/contrib/views/views_send/views_send.tokens.inc

@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Token integration for the Views Send module.
+ */
+
+/**
+ * Implements hook_token_info().
+ *
+ * These token are used by Rules and not in the Views form.
+ */
+function views_send_token_info() {
+  $data = array();
+  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;
+}