You are here

simplenews.module in Simplenews 7.2

Simplenews node handling, sent email, newsletter block and general hooks

File

simplenews.module
View source
<?php

/**
 * @file
 * Simplenews node handling, sent email, newsletter block and general hooks
 */

/**
 * NEWSLETTER MAIL PRIORITY
 */
define('SIMPLENEWS_PRIORITY_NONE', 0);
define('SIMPLENEWS_PRIORITY_HIGHEST', 1);
define('SIMPLENEWS_PRIORITY_HIGH', 2);
define('SIMPLENEWS_PRIORITY_NORMAL', 3);
define('SIMPLENEWS_PRIORITY_LOW', 4);
define('SIMPLENEWS_PRIORITY_LOWEST', 5);

/**
 * NEWSLETTER SEND COMMAND
 */
define('SIMPLENEWS_COMMAND_SEND_TEST', 0);
define('SIMPLENEWS_COMMAND_SEND_NOW', 1);
define('SIMPLENEWS_COMMAND_SEND_PUBLISH', 3);

/**
 * NEWSLETTER SUBSCRIPTION STATUS
 */
define('SIMPLENEWS_SUBSCRIPTION_STATUS_SUBSCRIBED', 1);
define('SIMPLENEWS_SUBSCRIPTION_STATUS_UNCONFIRMED', 2);
define('SIMPLENEWS_SUBSCRIPTION_STATUS_UNSUBSCRIBED', 0);

/**
 * NEWSLETTER SENT STATUS
 */
define('SIMPLENEWS_STATUS_SEND_NOT', 0);
define('SIMPLENEWS_STATUS_SEND_PENDING', 1);
define('SIMPLENEWS_STATUS_SEND_READY', 2);
define('SIMPLENEWS_STATUS_SEND_PUBLISH', 3);

/**
 * MAIL SPOOL SENT STATUS
 */
define('SIMPLENEWS_SPOOL_HOLD', 0);
define('SIMPLENEWS_SPOOL_PENDING', 1);
define('SIMPLENEWS_SPOOL_DONE', 2);
define('SIMPLENEWS_SPOOL_IN_PROGRESS', 3);

/**
 * AFTER EACH 100 NEWSLETTERS
 * simplenews_mail_spool() CHECKS IF LIMITS ARE EXCEEDED
 */
define('SIMPLENEWS_SEND_CHECK_INTERVAL', 100);

/**
 * AT 80% OF PHP MAX EXECUTION TIME EMAIL SENDING IS INTERRUPTED
 */
define('SIMPLENEWS_SEND_TIME_LIMIT', 0.8);

/**
 * SUBSCRIPTION STATUS
 */
define('SIMPLENEWS_SUBSCRIPTION_INACTIVE', 0);
define('SIMPLENEWS_SUBSCRIPTION_ACTIVE', 1);
define('SIMPLENEWS_OPT_INOUT_HIDDEN', 'hidden');
define('SIMPLENEWS_OPT_INOUT_SINGLE', 'single');
define('SIMPLENEWS_OPT_INOUT_DOUBLE', 'double');

/**
 * Used when sending an unlimited amount of mails from the spool.
 */
define('SIMPLENEWS_UNLIMITED', -1);

/**
 * Implements hook_permission().
 *
 * @todo Change sort order where required: http://drupal.org/node/224333#sorting_permissions
 */
function simplenews_permission() {
  return array(
    'administer newsletters' => array(
      'title' => t('Administer newsletters'),
    ),
    'administer simplenews subscriptions' => array(
      'title' => t('Administer simplenews subscriptions'),
    ),
    'administer simplenews settings' => array(
      'title' => t('Administer simplenews settings'),
    ),
    'send newsletter' => array(
      'title' => t('Send newsletter'),
    ),
    'subscribe to newsletters' => array(
      'title' => t('Subscribe to newsletters'),
    ),
  );
}

/**
 * Implements hook_init().
 *
 * @todo move this to a specific form theme function ?
 */
function simplenews_init() {
  drupal_add_css(drupal_get_path('module', 'simplenews') . '/simplenews.css', array(
    'every_page' => TRUE,
  ));
}

/**
 * Implements hook_menu().
 */
function simplenews_menu() {
  $items['admin/content/simplenews'] = array(
    'title' => 'Newsletters',
    'description' => 'List newsletters and newsletter sent status.',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_newsletter_issues',
    ),
    'access arguments' => array(
      'administer newsletters',
    ),
    'file' => 'includes/simplenews.admin.inc',
  );
  $items['admin/config/services/simplenews'] = array(
    'title' => 'Newsletters',
    'description' => 'Configure your sites newsletters.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_categories',
    ),
    'access arguments' => array(
      'administer newsletters',
    ),
    'file' => 'includes/simplenews.admin.inc',
  );
  $items['admin/config/services/simplenews/categories'] = array(
    'title' => 'Newsletters',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/config/services/simplenews/categories/%simplenews_newsletter/edit'] = array(
    'title' => 'Newsletters',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_newsletter_form',
      5,
    ),
    'access arguments' => array(
      'administer newsletters',
    ),
    'file' => 'includes/simplenews.admin.inc',
  );
  $items['admin/config/services/simplenews/categories/%simplenews_newsletter/delete'] = array(
    'title' => 'Newsletters',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_newsletter_delete',
      5,
    ),
    'access arguments' => array(
      'administer newsletters',
    ),
    'file' => 'includes/simplenews.admin.inc',
  );
  $items['admin/config/services/simplenews/add'] = array(
    'title' => 'Add newsletter',
    'type' => MENU_LOCAL_ACTION,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_newsletter_form',
    ),
    'access arguments' => array(
      'administer newsletters',
    ),
    'file' => 'includes/simplenews.admin.inc',
    'weight' => -9,
  );
  $items['admin/content/simplenews/subscriptions/delete'] = array(
    'title' => 'Delete',
    'type' => MENU_CALLBACK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_subscription_multiple_delete_confirm',
    ),
    'access arguments' => array(
      'administer simplenews subscriptions',
    ),
    'file' => 'includes/simplenews.admin.inc',
  );
  $items['admin/people/simplenews'] = array(
    'title' => 'Newsletter subscriptions',
    'description' => 'Newsletter subscription management.',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_subscription',
    ),
    'access arguments' => array(
      'administer simplenews subscriptions',
    ),
    'file' => 'includes/simplenews.admin.inc',
  );
  $items['admin/people/simplenews/users/edit/%'] = array(
    'title' => 'Subscriptions',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_subscriptions_admin_form',
      5,
    ),
    'access arguments' => array(
      'administer simplenews subscriptions',
    ),
    'file' => 'includes/simplenews.subscription.inc',
  );
  $items['admin/people/simplenews/import'] = array(
    'title' => 'Mass subscribe',
    'type' => MENU_LOCAL_ACTION,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_subscription_list_add',
    ),
    'access arguments' => array(
      'administer simplenews subscriptions',
    ),
    'file' => 'includes/simplenews.admin.inc',
    'weight' => 8,
  );
  $items['admin/people/simplenews/unsubscribe'] = array(
    'title' => 'Mass unsubscribe',
    'type' => MENU_LOCAL_ACTION,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_subscription_list_remove',
    ),
    'access arguments' => array(
      'administer simplenews subscriptions',
    ),
    'file' => 'includes/simplenews.admin.inc',
    'weight' => 9,
  );
  $items['admin/people/simplenews/export'] = array(
    'title' => 'Export',
    'type' => MENU_LOCAL_ACTION,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_subscription_list_export',
    ),
    'access arguments' => array(
      'administer simplenews subscriptions',
    ),
    'file' => 'includes/simplenews.admin.inc',
    'weight' => 10,
  );
  $items['admin/config/services/simplenews/settings'] = array(
    'title' => 'Settings',
    'description' => 'Simplenews settings.',
    'page callback' => 'drupal_get_form',
    'weight' => 20,
    'page arguments' => array(
      'simplenews_admin_settings_newsletter',
    ),
    'access arguments' => array(
      'administer simplenews settings',
    ),
    'file' => 'includes/simplenews.admin.inc',
    'file path' => drupal_get_path('module', 'simplenews'),
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/config/services/simplenews/settings/newsletter'] = array(
    'title' => 'Newsletter',
    'weight' => -15,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/config/services/simplenews/settings/subscription'] = array(
    'title' => 'Subscription',
    'description' => 'Subscription settings, opt-in/out confirmation email text.',
    'page callback' => 'drupal_get_form',
    'weight' => -10,
    'page arguments' => array(
      'simplenews_admin_settings_subscription',
    ),
    'access arguments' => array(
      'administer simplenews settings',
    ),
    'file' => 'includes/simplenews.admin.inc',
    'file path' => drupal_get_path('module', 'simplenews'),
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/config/services/simplenews/settings/mail'] = array(
    'title' => 'Send mail',
    'description' => 'Send mail, cron and debug options.',
    'page callback' => 'drupal_get_form',
    'weight' => -5,
    'page arguments' => array(
      'simplenews_admin_settings_mail',
    ),
    'access arguments' => array(
      'administer simplenews settings',
    ),
    'file' => 'includes/simplenews.admin.inc',
    'file path' => drupal_get_path('module', 'simplenews'),
    'type' => MENU_LOCAL_TASK,
  );

  // Explicitly define add and remove menu routers even if they are are equal
  // to avoid conflicts with the different combined arguments.
  $items['newsletter/confirm/add/%/%/%/%'] = array(
    'title' => 'Confirm newsletter subscriptions',
    'type' => MENU_CALLBACK,
    'page callback' => 'simplenews_confirm_subscription',
    'page arguments' => array(
      2,
      3,
      4,
      5,
      6,
    ),
    'access arguments' => array(
      'subscribe to newsletters',
    ),
    'file' => 'includes/simplenews.subscription.inc',
  );
  $items['newsletter/confirm/remove/%/%/%/%'] = $items['newsletter/confirm/add/%/%/%/%'];
  $items['newsletter/confirm/combined/%/%/%'] = array(
    'title' => 'Confirm newsletter subscriptions',
    'type' => MENU_CALLBACK,
    'page callback' => 'simplenews_confirm_combined',
    'page arguments' => array(
      3,
      4,
      5,
    ),
    'access arguments' => array(
      'subscribe to newsletters',
    ),
    'file' => 'includes/simplenews.subscription.inc',
  );

  // Backwards compatibility for old links.
  $items['newsletter/confirm'] = array(
    'title' => 'Confirm newsletter subscriptions',
    'type' => MENU_CALLBACK,
    'page callback' => 'simplenews_old_confirm_subscription',
    'page arguments' => array(),
    'access arguments' => array(
      'subscribe to newsletters',
    ),
    'file' => 'includes/simplenews.subscription.inc',
  );
  $items['newsletter/subscriptions'] = array(
    'title' => 'Your newsletter subscriptions',
    'type' => MENU_CALLBACK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_subscriptions_page_form',
    ),
    'access arguments' => array(
      'subscribe to newsletters',
    ),
    'file' => 'includes/simplenews.subscription.inc',
  );
  $items['node/%node/simplenews'] = array(
    'title' => 'Newsletter',
    'type' => MENU_LOCAL_TASK,
    'access callback' => 'simplenews_node_tab_access',
    'access arguments' => array(
      1,
    ),
    'page callback' => 'simplenews_node_tab_page',
    'page arguments' => array(
      1,
    ),
    'context' => MENU_LOCAL_TASK,
    'file' => 'includes/simplenews.admin.inc',
    'weight' => 2,
  );
  return $items;
}

/**
 * Implements hook_admin_paths().
 */
function simplenews_admin_paths() {
  $paths = array(
    'node/*/simplenews' => TRUE,
  );
  return $paths;
}

/**
 * Implements hook_node_type_delete().
 */
function simplenews_node_type_delete($info) {
  drupal_static_reset('simplenews_get_content_types');
}

/**
 * Implements hook_node_type_update().
 */
function simplenews_node_type_update($info) {
  drupal_static_reset('simplenews_get_content_types');
  if (simplenews_check_node_types($info->type)) {
    simplenews_issue_fields_add($info);
  }
  else {

    // Don't remove the field. This leads to data loss.
  }
}

/**
 * Implements hook_node_type_insert().
 */
function simplenews_node_type_insert($info) {

  // Avoid using simplenews_check_node_type() because the node type cache
  // has not been cleared yet.
  if (variable_get('simplenews_content_type_' . $info->type, FALSE)) {
    simplenews_issue_fields_add($info);
  }
}

/**
 * Implements hook_node_view().
 */
function simplenews_node_view($node, $view_mode) {
  if (!simplenews_check_node_types($node->type)) {
    return;
  }

  // Only do token replacements for view modes other than the our own email view
  // modes. Token replacements for them will happen later on.
  if (strpos($view_mode, 'email_') !== FALSE) {
    return;
  }

  // Build up content, add as much as there is.
  $context = array(
    'node' => $node,
  );

  // If the current user is a subscriber, extend context.
  if ($GLOBALS['user']->uid > 0 && ($subscriber = simplenews_subscriber_load_by_mail($GLOBALS['user']->mail))) {
    $context['simplenews_subscriber'] = $subscriber;
  }

  // Loop over all render array elements.
  foreach (element_children($node->content) as $key) {
    $element =& $node->content[$key];

    // Make sure this is a field.
    if (!isset($element['#field_type'])) {
      continue;
    }

    // Loop over all field values.
    foreach (element_children($element) as $field_key) {
      $item =& $element[$field_key];

      // Only fields which result in simple markup elements are supported for
      // token replacements for now.
      if (isset($item['#markup'])) {
        $item['#markup'] = token_replace($item['#markup'], $context, array());
      }
    }
  }
}

/**
 * Implements hook_node_update().
 */
function simplenews_node_update($node) {
  if (!simplenews_check_node_types($node->type)) {
    return;
  }

  // Check if the newsletter is set to send on publish and needs to be send.
  if (simplenews_issue_status($node) == SIMPLENEWS_STATUS_SEND_PUBLISH && $node->status == NODE_PUBLISHED) {
    module_load_include('inc', 'simplenews', 'includes/simplenews.mail');
    simplenews_add_node_to_spool($node);
  }
}

/**
 * Implements hook_node_delete().
 */
function simplenews_node_delete($node) {
  if (!simplenews_check_node_types($node->type)) {
    return;
  }

  // Check if pending emails of this newsletter issue exist and delete these too.
  module_load_include('inc', 'simplenews', 'includes/simplenews.mail');
  $count = simplenews_delete_spool(array(
    'entity_id' => $node->nid,
    'entity_type' => 'node',
  ));
  if ($count) {
    drupal_set_message(t('Newsletter %title was deleted but had @count pending emails. They can no longer be sent.', array(
      '%title' => $node->title,
      '@count' => $count,
    )), 'warning');
    watchdog('simplenews', 'Newsletter %title deleted with @count pending emails. Emails can no longer be sent.', array(
      '%title' => $node->title,
      '@count' => $count,
    ), WATCHDOG_ALERT);
  }
  drupal_set_message(t('Newsletter %title was deleted.', array(
    '%title' => $node->title,
  )));
}

/**
 * Check if content type(s) is enabled for use as Simplenews newsletter.
 *
 * @param $types
 *   Array of content types or single content type string.
 * @return boolean
 *   TRUE if at least one of $types is enabled for Simplenews.
 *
 * @ingroup issue
 */
function simplenews_check_node_types($types) {
  if (!is_array($types)) {
    $types = array(
      $types,
    );
  }
  if ($sn_types = simplenews_get_content_types()) {
    foreach ($types as $type) {
      if (in_array($type, $sn_types)) {
        return TRUE;
      }
    }
  }
  return FALSE;
}

/**
 * Get all node types supported by Simplenews.
 *
 * @return
 *   Array of node-types which can be used a simplenews newsletter issue.
 *
 * @ingroup issue
 */
function simplenews_get_content_types() {
  $simplenews_types =& drupal_static(__FUNCTION__, array());
  if (!$simplenews_types) {
    foreach (node_type_get_types() as $name => $type) {
      if (variable_get('simplenews_content_type_' . $name, FALSE)) {
        $simplenews_types[] = $name;
      }
    }
  }
  return $simplenews_types;
}

/**
 * Add fields for newsletter, status and sent count to a note type.
 *
 * @param $type
 *   A node type object.
 *
 * @ingroup issue
 */
function simplenews_issue_fields_add($type) {
  simplenews_issue_newsletter_field_add($type);
  simplenews_issue_handler_field_add($type);
  simplenews_issue_handler_settings_field_add($type);
  simplenews_issue_status_field_add($type);
  simplenews_issue_sent_count_field_add($type);
}

/**
 * Add field for newsletter
 *
 * @param $type A node type object.
 *
 * @ingroup issue
 */
function simplenews_issue_newsletter_field_add($type) {
  $field_name = variable_get('simplenews_newsletter_field', 'simplenews_newsletter');
  $field = field_info_field($field_name);
  $instance = field_info_instance('node', $field_name, $type->type);
  if (empty($field)) {
    $field = array(
      'field_name' => $field_name,
      'type' => 'entityreference',
      'cardinality' => 1,
      'entity_types' => array(
        'node',
      ),
      'translatable' => FALSE,
      'settings' => array(
        'target_type' => 'simplenews_newsletter',
        'handler' => 'base',
        'handler_submit' => 'Change handler',
        'handler_settings' => array(
          'sort' => array(
            'type' => 'property',
            'property' => 'weight',
            'direction' => 'ASC',
          ),
          'behaviors' => array(
            'views-select-list' => array(
              'status' => 1,
            ),
          ),
        ),
      ),
    );
    $field = field_create_field($field);
  }
  if (empty($instance)) {
    $newsletter = simplenews_newsletter_get_all();
    $default_value = array();
    if ($newsletter) {
      $newsletter = reset($newsletter);
      $default_value = array(
        array(
          'target_id' => $newsletter->newsletter_id,
        ),
      );
    }
    $instance = array(
      'label' => t('Newsletter'),
      'field_name' => $field_name,
      'bundle' => $type->type,
      'entity_type' => 'node',
      'required' => TRUE,
      'default_value' => $default_value,
      'widget' => array(
        'type' => 'options_select',
      ),
      'display' => array(
        'default' => array(
          'label' => 'hidden',
          'type' => 'entityreference_label',
        ),
        'teaser' => array(
          'label' => 'hidden',
          'type' => 'hidden',
        ),
        'email_plain' => array(
          'label' => 'hidden',
          'type' => 'hidden',
        ),
        'email_html' => array(
          'label' => 'hidden',
          'type' => 'hidden',
        ),
        'email_textalt' => array(
          'label' => 'hidden',
          'type' => 'hidden',
        ),
      ),
    );
    field_create_instance($instance);
  }
}

/**
 * Add a field for the recipient handler choice.
 *
 * @param $type A node type object
 *
 * @ingroup issue
 */
function simplenews_issue_handler_field_add($type) {
  $field_name = variable_get('simplenews_handler_field', 'simplenews_handler');
  $field = field_info_field($field_name);
  $instance = field_info_instance('node', $field_name, $type->type);
  if (empty($field)) {
    $field = array(
      'field_name' => $field_name,
      'type' => 'list_text',
      'cardinality' => 1,
      'entity_types' => array(
        'node',
      ),
      'translatable' => FALSE,
      'settings' => array(
        'allowed_values_function' => 'simplenews_handler_field_allowed_values',
      ),
    );
    $field = field_create_field($field);
  }
  if (empty($instance)) {

    // We set the default value to simplenews_subscription
    $instance = array(
      'label' => t('Recipient Handler'),
      'field_name' => $field_name,
      'bundle' => $type->type,
      'entity_type' => 'node',
      'required' => TRUE,
      'default_value' => array(
        array(
          'value' => 'simplenews_subscription',
        ),
      ),
      'widget' => array(
        'type' => 'options_select',
      ),
      'display' => array(
        'default' => array(
          'label' => 'hidden',
          'type' => 'hidden',
        ),
      ),
    );
    field_create_instance($instance);
  }
}

/**
 * Add a field for the recipient handler settings.
 *
 * @param $type A node type object
 *
 * @ingroup issue
 */
function simplenews_issue_handler_settings_field_add($type) {
  $field_name = variable_get('simplenews_handler_settings_field', 'simplenews_handler_settings');
  $field = field_info_field($field_name);
  $instance = field_info_instance('node', $field_name, $type->type);
  if (empty($field)) {
    $field = array(
      'field_name' => $field_name,
      'type' => 'text_long',
      'cardinality' => 1,
      'entity_types' => array(
        'node',
      ),
      'translatable' => FALSE,
      'settings' => array(),
      'default_formatter' => 'hidden',
    );
    $field = field_create_field($field);
  }
  if (empty($instance)) {

    // We set the default value to simplenews_subscription
    $instance = array(
      'label' => t('Recipient Handle Settings'),
      'field_name' => $field_name,
      'bundle' => $type->type,
      'entity_type' => 'node',
      'widget' => array(
        'type' => 'text_textarea',
      ),
      'display' => array(
        'default' => array(
          'label' => 'hidden',
          'type' => 'hidden',
        ),
      ),
    );
    field_create_instance($instance);
  }
}

/**
 * Returns the array of allowed values for the simplenews_newsletter_handler
 * field.
 *
 * @see list_allowed_values
 */
function simplenews_handler_field_allowed_values($field = array(), $instance = NULL, $entity_type = NULL, $entity = NULL) {
  ctools_include('plugins');
  $handlers = ctools_get_plugins('simplenews', 'recipient_handlers');
  $allowed_values = array();
  foreach ($handlers as $handler => $settings) {
    $allowed_values[$handler] = filter_xss($settings['title']);
  }
  return $allowed_values;
}

/**
 * Add field for status
 *
 * @param $type A node type object.
 *
 * @ingroup issue
 */
function simplenews_issue_status_field_add($type) {
  $field_name = variable_get('simplenews_issue_status_field', 'simplenews_issue_status');
  $field = field_info_field($field_name);
  $instance = field_info_instance('node', $field_name, $type->type);
  if (empty($field)) {
    $field = array(
      'field_name' => $field_name,
      'type' => 'number_integer',
      'cardinality' => 1,
      'entity_types' => array(
        'node',
      ),
      'translatable' => FALSE,
      'settings' => array(),
    );
    $field = field_create_field($field);
  }
  if (empty($instance)) {
    $instance = array(
      'label' => t('Status'),
      'field_name' => $field_name,
      'bundle' => $type->type,
      'entity_type' => 'node',
      'display' => array(
        'default' => array(
          'label' => 'hidden',
          'type' => 'hidden',
        ),
        'teaser' => array(
          'label' => 'hidden',
          'type' => 'hidden',
        ),
        'email_plain' => array(
          'label' => 'hidden',
          'type' => 'hidden',
        ),
        'email_html' => array(
          'label' => 'hidden',
          'type' => 'hidden',
        ),
        'email_textalt' => array(
          'label' => 'hidden',
          'type' => 'hidden',
        ),
      ),
    );
    field_create_instance($instance);
  }
}

/**
 * Add field for status
 *
 * @param $type A node type object.
 *
 * @ingroup issue
 */
function simplenews_issue_sent_count_field_add($type) {
  $field_name = variable_get('simplenews_sent_count_field', 'simplenews_sent_count');
  $field = field_info_field($field_name);
  $instance = field_info_instance('node', $field_name, $type->type);
  if (empty($field)) {
    $field = array(
      'field_name' => $field_name,
      'type' => 'number_integer',
      'cardinality' => 1,
      'entity_types' => array(
        'node',
      ),
      'translatable' => FALSE,
      'settings' => array(),
    );
    $field = field_create_field($field);
  }
  if (empty($instance)) {
    $instance = array(
      'label' => t('Sent subscriber count'),
      'field_name' => $field_name,
      'bundle' => $type->type,
      'entity_type' => 'node',
      'display' => array(
        'default' => array(
          'label' => 'hidden',
          'type' => 'hidden',
        ),
        'teaser' => array(
          'label' => 'hidden',
          'type' => 'hidden',
        ),
        'email_plain' => array(
          'label' => 'hidden',
          'type' => 'hidden',
        ),
        'email_html' => array(
          'label' => 'hidden',
          'type' => 'hidden',
        ),
        'email_textalt' => array(
          'label' => 'hidden',
          'type' => 'hidden',
        ),
      ),
    );
    field_create_instance($instance);
  }
}

/**
 * Remove fields for newsletter, status and sent count to a note type.
 *
 * @param $type
 *   A node type object.
 *
 * @ingroup issue
 */
function simplenews_issue_fields_remove($type) {
  $field_name = variable_get('simplenews_newsletter_field', 'simplenews_newsletter');
  $instance = field_info_instance('node', $field_name, $type->type);
  if ($instance) {
    field_delete_instance($instance);
  }
  $field_name = variable_get('simplenews_issue_status_field', 'simplenews_issue_status');
  $instance = field_info_instance('node', $field_name, $type->type);
  if ($instance) {
    field_delete_instance($instance);
  }
  $field_name = variable_get('simplenews_sent_count_field', 'simplenews_sent_count');
  $instance = field_info_instance('node', $field_name, $type->type);
  if ($instance) {
    field_delete_instance($instance);
  }
}

/**
 * Get the fieldname(s) from a content type that hold the newsletter.
 *
 * @param $bundle_name
 *   The content type of which to return the field.
 * @return field definition
 *   Field name of the field containing the newsletter, FALSE if no field is
 *   selected.
 *
 * @ingroup issue
 */
function simplenews_get_newsletter_field($bundle_name) {
  $fields_info = field_info_fields();
  $field_name = variable_get('simplenews_newsletter_field', 'simplenews_newsletter');
  $instances = field_info_instances('node', $bundle_name);
  if (isset($instances[$field_name])) {
    return $fields_info[$field_name];
  }
  else {
    return FALSE;
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Add checkbox to the content type form to use the content type as newsletter.
 */
function simplenews_form_node_type_form_alter(&$form, $form_state) {

  // Add option to use content type as simplenews newsletter.
  $form['workflow']['simplenews_content_type'] = array(
    '#type' => 'checkbox',
    '#title' => t('Use as simplenews newsletter'),
    '#default_value' => variable_get('simplenews_content_type_' . $form['#node_type']->type, 0),
  );
}

/**
 * Implements hook_form_alter().
 */
function simplenews_form_alter(&$form, &$form_state, $form_id) {

  // Add Simplenews settings to simplenews newsletter node form.
  if (!empty($form['#node_edit_form'])) {
    if (in_array($form['type']['#value'], simplenews_get_content_types())) {
      _simplenews_node_form($form, $form_state);
    }
  }
}

/**
 * @todo
 */
function _simplenews_node_form(&$form, $form_state) {

  // Display warning if the node is currently being sent.
  if (!empty($form['#node']->nid)) {
    if (simplenews_issue_status(node_load($form['#node']->nid)) == SIMPLENEWS_STATUS_SEND_PENDING) {
      drupal_set_message(t('This newsletter issue is currently being sent. Any changes will be reflected in the e-mails which have not been sent yet.'), 'warning');
    }
  }
  if (module_exists('token')) {
    $form['simplenews_token_help'] = array(
      '#title' => t('Replacement patterns'),
      '#type' => 'fieldset',
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#description' => t('These tokens can be used in all text fields except subject and will be replaced on-screen and in the email.'),
    );
    $form['simplenews_token_help']['browser'] = array(
      '#theme' => 'token_tree',
      '#token_types' => array(
        'simplenews-newsletter',
        'simplenews-subscriber',
        'node',
      ),
    );
  }
}

/**
 * Implements hook_entity_info().
 */
function simplenews_entity_info() {
  return array(
    'simplenews_newsletter' => array(
      'label' => t('Simplenews newsletter'),
      'entity class' => 'SimplenewsNewsletter',
      'controller class' => 'SimplenewsNewsletterController',
      'metadata controller class' => 'SimplenewsNewsletterMetadataController',
      'views controller class' => 'EntityDefaultViewsController',
      'base table' => 'simplenews_newsletter',
      'fieldable' => TRUE,
      'entity keys' => array(
        'id' => 'newsletter_id',
        'label' => 'name',
      ),
      'label callback' => 'entity_class_label',
      'uri callback' => 'entity_class_uri',
      'view modes' => array(
        'full' => array(
          'label' => t('Default'),
          'custom settings' => FALSE,
        ),
      ),
      'access callback' => 'simplenews_newsletter_access',
    ),
    'simplenews_subscriber' => array(
      'label' => t('Simplenews subscriber'),
      'entity class' => 'SimplenewsSubscriber',
      'controller class' => 'SimplenewsSubscriberController',
      'metadata controller class' => 'SimplenewsSubscriberMetadataController',
      'views controller class' => 'EntityDefaultViewsController',
      'base table' => 'simplenews_subscriber',
      'fieldable' => TRUE,
      'entity keys' => array(
        'id' => 'snid',
        'label' => 'mail',
      ),
      'label callback' => 'entity_class_label',
      'uri callback' => 'entity_class_uri',
      'view modes' => array(
        'full' => array(
          'label' => t('Default'),
          'custom settings' => FALSE,
        ),
      ),
      'access callback' => 'simplenews_subscriber_access',
    ),
  );
}

/**
 * Entity access callback for simplenews newsletter entity.
 */
function simplenews_newsletter_access($op, SimplenewsNewsletter $newsletter, $account = NULL) {
  if ($op == 'view') {
    return TRUE;
  }
  return user_access('administer newsletters');
}

/**
 * Entity access callback for simplenews newsletter entity.
 */
function simplenews_subscriber_access($op, SimplenewsSubscriber $subscriber, $account = NULL) {
  return user_access('administer simplenews subscriptions');
}

/**
 * Implements hook_entity_info_alter().
 */
function simplenews_entity_info_alter(&$info) {

  // Add the 'Plain', 'HTML' and 'Text alternative' view mode for nodes in email.
  $info['node']['view modes'] += array(
    'email_plain' => array(
      'label' => t('Email: Plain'),
      'custom settings' => FALSE,
    ),
    'email_html' => array(
      'label' => t('Email: HTML'),
      'custom settings' => FALSE,
    ),
    'email_textalt' => array(
      'label' => t('Email: HTML text alternative'),
      'custom settings' => FALSE,
    ),
  );
}

/**
 * Implements hook_cron().
 */
function simplenews_cron() {
  module_load_include('inc', 'simplenews', 'includes/simplenews.mail');
  simplenews_mail_spool(variable_get('simplenews_throttle', 20));
  simplenews_clear_spool();

  // Update sent status for newsletter admin panel.
  simplenews_send_status_update();
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Add simplenews subscription fields to user register form.
 * @todo mode this function to another place in the module.
 */
function simplenews_form_user_register_form_alter(&$form, &$form_state) {
  $options = $default_value = $hidden = array();

  // Determine the lists to which a user can choose to subscribe.
  // Determine to which other list a user is automatically subscribed.
  foreach (simplenews_newsletter_get_all() as $newsletter) {
    $subscribe_new_account = $newsletter->new_account;
    $opt_inout_method = $newsletter->opt_inout;
    if (($subscribe_new_account == 'on' || $subscribe_new_account == 'off') && ($opt_inout_method == 'single' || $opt_inout_method == 'double')) {
      $options[$newsletter->newsletter_id] = $newsletter->name;
      if ($subscribe_new_account == 'on') {
        $default_value[] = $newsletter->newsletter_id;
      }
    }
    else {
      if ($subscribe_new_account == 'silent' || $subscribe_new_account == 'on' && $opt_inout_method == SIMPLENEWS_OPT_INOUT_HIDDEN) {
        $hidden[] = $newsletter->newsletter_id;
      }
    }
  }
  if (count($options)) {

    // @todo Change this text: use less words;
    $form['simplenews'] = array(
      '#type' => 'fieldset',
      '#description' => t('Select the newsletter(s) to which you wish to subscribe.'),
      '#weight' => 5,
    );
    $form['simplenews']['newsletters'] = array(
      '#type' => 'checkboxes',
      '#options' => $options,
      '#default_value' => $default_value,
    );
  }
  if (count($hidden)) {
    $form['simplenews_hidden'] = array(
      '#type' => 'hidden',
      '#value' => implode(',', $hidden),
    );
  }
}

/**
 * Implements hook_user_insert().
 *
 * Update uid and preferred language when the new account was already subscribed.
 */
function simplenews_user_insert(&$edit, $account, $newsletter) {

  // Use the email address to check if new account is already subscribed.
  $subscriber = simplenews_subscriber_load_by_mail($edit['mail']);

  // If the user is subscribed, we update the subscriber with uid and language.
  if ($subscriber) {
    $subscriber->uid = $edit['uid'];
    $subscriber->language = $edit['language'];
    $subscriber->activated = 1;
    simplenews_subscriber_save($subscriber);
  }

  // Process subscription check boxes.
  if (isset($edit['newsletters'])) {
    $newsletter_ids = array_keys(array_filter($edit['newsletters']));
    if (!empty($newsletter_ids)) {
      $newsletters = simplenews_newsletter_load_multiple($newsletter_ids);
      foreach ($newsletters as $newsletter) {
        simplenews_subscribe($account->mail, $newsletter->newsletter_id, FALSE, 'website', $edit['language']);
        drupal_set_message(t('You have been subscribed to %newsletter.', array(
          '%newsletter' => $newsletter->name,
        )));
      }
    }
  }

  // Process hidden (automatic) subscriptions.
  if (isset($edit['simplenews_hidden'])) {
    foreach (explode(',', $edit['simplenews_hidden']) as $newsletter_id) {
      simplenews_subscribe($account->mail, $newsletter_id, FALSE, 'automatically', $edit['language']);
    }
    unset($edit['simplenews_hidden']);
  }

  // set inactive if not created by an administrator.
  // @todo This needs a cleaner API.
  if (!user_access('administer users')) {

    // this user will be activated on first login (see simplenews_user_login)
    $query = db_update('simplenews_subscriber')
      ->fields(array(
      'activated' => SIMPLENEWS_SUBSCRIPTION_INACTIVE,
    ))
      ->condition('uid', $account->uid)
      ->execute();
  }
}

/**
 * Implements hook_user_login().
 *
 * Subscribe user to a newsletter as per registration form.
 */
function simplenews_user_login(&$edit, $account) {

  // Subscriptions of users that did sign up by themselves have to be
  // activated first at their first login (-> account::access = 0)
  if ($account->access == 0) {
    $query = db_update('simplenews_subscriber')
      ->fields(array(
      'activated' => SIMPLENEWS_SUBSCRIPTION_ACTIVE,
    ))
      ->condition('uid', $account->uid)
      ->execute();
  }
}

/**
 * Implements hook_user_presave().
 *
 * User data (mail, status, language) is synchronized with subscriber.
 * This function handles existing user account, simplenews_user_insert takes
 * care of new accounts.
 * @see simplenews_user_insert()
 */
function simplenews_user_presave(&$edit, $account, $newsletter) {
  switch ($newsletter) {
    case 'account':

      // We only process existing accounts.
      if (!empty($account->uid)) {
        $subscriber = simplenews_subscriber_load_by_uid($account->uid);
        if ($subscriber) {

          // Update mail, status and language if they are changed.
          if (isset($edit['mail'])) {
            $subscriber->mail = $edit['mail'];
          }
          if (isset($edit['status']) && variable_get('simplenews_sync_account', TRUE)) {
            $subscriber->activated = $edit['status'];
          }
          if (isset($edit['language'])) {
            $subscriber->language = $edit['language'];
          }
          simplenews_subscriber_save($subscriber);
        }
      }
      break;
  }
}

/**
 * Implements hook_user_cancel().
 */
function simplenews_user_cancel($edit, $account, $method) {

  // Deactivate subscriber when account is disabled via cancel user.
  if ($account) {
    $query = db_update('simplenews_subscriber')
      ->fields(array(
      'activated' => SIMPLENEWS_SUBSCRIPTION_INACTIVE,
    ))
      ->condition('uid', $account->uid)
      ->execute();
  }
}

/**
 * Implements hook_user_delete().
 */
function simplenews_user_delete($account) {

  // Delete subscription when account is removed.
  $subscriber = simplenews_subscriber_load_by_mail($account->mail);
  if ($subscriber) {
    simplenews_subscriber_delete($subscriber->snid);
  }
}

/**
 * Implements hook_user_categories().
 */
function simplenews_user_categories() {
  $output[] = array(
    'name' => 'simplenews',
    'title' => t('Newsletters'),
    'weight' => 10,
    'access callback' => 'simplenews_subscription_edit_access',
  );
  return $output;
}

/**
 * Access callback for user newsletter editing.
 */
function simplenews_subscription_edit_access($account) {
  global $user;

  // Disallow anonymous.
  if (!$account || $account->uid == 0) {
    return FALSE;
  }

  // Allow edit own subscription.
  if ($user->uid == $account->uid) {
    if (user_access('subscribe to newsletters', $account)) {
      return TRUE;
    }
  }

  // Allow administrator to edit account's subscription.
  if (user_access('administer users')) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Add simplenews subscription management form to account 'Newsletters'
 */
function simplenews_form_user_profile_form_alter(&$form, &$form_state) {
  if ($form['#user_category'] == 'simplenews') {
    $subscription = simplenews_subscriber_load_by_mail($form['#user']->mail);
    module_load_include('inc', 'simplenews', 'includes/simplenews.subscription');
    simplenews_subscriptions_account_form($form, $form_state, $subscription);
    $form['subscriptions']['#title'] = t('Newsletter subscriptions');
    unset($form['update'], $form['subscriptions']['mail']);
  }
}

/**
 * Implements hook_user_view().
 */
function simplenews_user_view($account, $build_mode) {
  global $user;
  if ($user->uid == $account->uid || user_access('administer users')) {
    $account->content['simplenews'] = array(
      '#type' => 'user_profile_category',
      '#title' => t('Newsletters'),
    );

    // Collect newsletter to which the current user is subscribed.
    // 'hidden' newsletters are not listed.
    $newsletters = simplenews_newsletter_get_visible();
    $subscription = simplenews_subscriber_load_by_mail($account->mail);
    foreach ($newsletters as $newsletter) {
      if (isset($subscription->newsletter_subscription[$newsletter->newsletter_id]) && $subscription->newsletter_subscription[$newsletter->newsletter_id]->status == TRUE) {
        $links[] = l($newsletter->name, 'newsletter/' . $newsletter->newsletter_id);
      }
    }
    if (isset($links)) {

      // @todo replace with theme('links', $links) to form a list of newsletters?
      $links = implode(', ', $links);
    }
    else {
      $links = t('None');
    }

    // When a user has no permission to subscribe and is not subscribed
    // we do not display the 'no subscriptions' message.
    if (user_access('subscribe to newsletters') || $links != t('None')) {
      $account->content['simplenews']['subscriptions'] = array(
        '#type' => 'user_profile_item',
        '#title' => t('Subscribed to'),
        '#markup' => $links,
      );
    }
    if (user_access('subscribe to newsletters')) {
      $account->content['simplenews']['my_newsletters'] = array(
        '#type' => 'user_profile_item',
        '#title' => '',
        '#markup' => t('Manage <a href="!url">subscriptions</a>', array(
          '!url' => url('user/' . $account->uid . '/edit/simplenews'),
        )),
      );
    }
  }
}

/**
 * Implements hook_block_info().
 */
function simplenews_block_info() {
  $blocks = array();

  // Special block for multi
  $blocks[0] = array(
    'info' => t('Newsletter: Multi Subscription'),
  );

  // Only list a block if the newsletter is not 'hidden' and marked to provide a block.
  $newsletters = simplenews_newsletter_get_visible(array(
    'block' => '1',
  ));
  if ($newsletters) {
    foreach ($newsletters as $newsletter) {

      // @todo 1. without form -> by role; 2. with form -> user caching with
      // refresh on subscribe/unsubscribe (option as setting) or no caching.
      $blocks[$newsletter->newsletter_id] = array(
        'info' => t('Newsletter: @title', array(
          '@title' => $newsletter->name,
        )),
        // @todo Use block's own settings?
        'cache' => variable_get('simplenews_block_f_' . $newsletter->newsletter_id, 1) ? DRUPAL_NO_CACHE : DRUPAL_CACHE_PER_ROLE,
      );
    }
  }
  return $blocks;
}

/**
 * Implements hook_block_configure().
 */
function simplenews_block_configure($delta = '') {

  // Special block for multi
  if ($delta == 0) {
    $form['simplenews_block_multiple']['simplenews_block_m_multiple'] = array(
      '#type' => 'textfield',
      '#title' => t('Block message'),
      '#size' => 60,
      '#maxlength' => 255,
      // @todo: clean localization / i18n needed
      '#default_value' => variable_get('simplenews_block_m_multiple', t('Select the newsletter(s) to which you want to subscribe or unsubscribe.')),
    );
  }
  else {
    $form['simplenews_block_' . $delta]['simplenews_block_m_' . $delta] = array(
      '#type' => 'textfield',
      '#title' => t('Block message'),
      '#size' => 60,
      '#maxlength' => 255,
      '#default_value' => variable_get('simplenews_block_m_' . $delta, t('Stay informed on our latest news!')),
    );
    $form['simplenews_block_' . $delta]['simplenews_block_f_' . $delta] = array(
      '#type' => 'radios',
      '#title' => t('Subscription interface'),
      '#options' => array(
        '1' => t('Subscription form'),
        '0' => t('Link to form'),
      ),
      '#description' => t("Note: this requires permission 'subscribe to newsletters'."),
      '#default_value' => variable_get('simplenews_block_f_' . $delta, 1),
    );
    if (module_exists('views')) {
      $form['simplenews_block_' . $delta]['simplenews_block_l_' . $delta] = array(
        '#type' => 'checkbox',
        '#title' => t('Display link to previous issues'),
        '#return_value' => 1,
        '#default_value' => variable_get('simplenews_block_l_' . $delta, 1),
        '#description' => t('Link points to newsletter/newsletter_id, which is provided by the newsletter issue list default view.'),
      );
    }
    $form['simplenews_block_' . $delta]['simplenews_block_i_status_' . $delta] = array(
      '#type' => 'checkbox',
      '#title' => t('Display previous issues'),
      '#return_value' => 1,
      '#default_value' => variable_get('simplenews_block_i_status_' . $delta, 0),
    );
    $form['simplenews_block_' . $delta]['simplenews_block_i_' . $delta] = array(
      '#type' => 'select',
      '#title' => t('Number of issues to display'),
      '#options' => drupal_map_assoc(array(
        1,
        2,
        3,
        4,
        5,
        6,
        7,
        8,
        9,
        10,
      )),
      '#default_value' => variable_get('simplenews_block_i_' . $delta, 5),
      '#states' => array(
        'visible' => array(
          ":input[name='simplenews_block_i_status_{$delta}']" => array(
            'checked' => TRUE,
          ),
        ),
      ),
    );
    if (module_exists('views')) {
      $form['simplenews_block_' . $delta]['simplenews_block_r_' . $delta] = array(
        '#type' => 'checkbox',
        '#title' => t('Display RSS-feed icon'),
        '#return_value' => 1,
        '#default_value' => variable_get('simplenews_block_r_' . $delta, 1),
        '#description' => t('Link points to newsletter/feed/newsletter_id, which is provided by the newsletter issue list default view.'),
      );
    }
  }
  return $form;
}

/**
 * Implements hook_block_save().
 */
function simplenews_block_save($delta = '', $edit = array()) {
  if ($delta == 0) {
    variable_set('simplenews_block_m_multiple', $edit['simplenews_block_m_multiple']);
  }
  else {
    variable_set('simplenews_block_m_' . $delta, $edit['simplenews_block_m_' . $delta]);
    variable_set('simplenews_block_f_' . $delta, $edit['simplenews_block_f_' . $delta]);
    if (isset($edit['simplenews_block_l_' . $delta])) {
      variable_set('simplenews_block_l_' . $delta, $edit['simplenews_block_l_' . $delta]);
    }
    variable_set('simplenews_block_i_status_' . $delta, $edit['simplenews_block_i_status_' . $delta]);
    variable_set('simplenews_block_i_' . $delta, $edit['simplenews_block_i_' . $delta]);
    if (isset($edit['simplenews_block_r_' . $delta])) {
      variable_set('simplenews_block_r_' . $delta, $edit['simplenews_block_r_' . $delta]);
    }
  }
}

/**
 * Implements hook_block_view().
 */
function simplenews_block_view($delta = '') {

  // Special block for multi
  if ($delta == 0) {
    if (user_access('subscribe to newsletters')) {
      module_load_include('inc', 'simplenews', 'includes/simplenews.subscription');

      // render block only if permitted
      $block = array(
        'subject' => t('Newsletters'),
        'content' => theme('simplenews_multi_block'),
      );
      return $block;
    }
    return NULL;
  }
  else {
    $newsletters = simplenews_newsletter_get_visible(array(
      'block' => '1',
    ));

    // Only display a block if $delta is a valid newsletter id.
    if (in_array($delta, array_keys($newsletters))) {

      // $delta is validated, the block can be displayed.
      $block = array(
        'subject' => check_plain($newsletters[$delta]->name),
        'content' => theme(array(
          'simplenews_block__' . $delta,
          'simplenews_block',
        ), array(
          'newsletter_id' => $delta,
        )),
      );
      return $block;
    }
  }
}

/**
 * Implements hook_forms().
 *
 * All form blocks are build using simplenews_block_form().
 * hook_forms() is required to provide unique form id for each block form.
 */
function simplenews_forms($form_id, $args) {
  $forms = array();
  $match = preg_match('/simplenews_block_form_(\\d+)/', $form_id, $matches);
  if ($match) {
    module_load_include('inc', 'simplenews', 'includes/simplenews.subscription');
    $forms['simplenews_block_form_' . $matches[1]] = array(
      'callback' => 'simplenews_block_form',
      'callback arguments' => array(
        $matches[1],
      ),
    );
  }
  return $forms;
}

/**
 * Load a user or creates a dummy anonymous user.
 *
 * @return
 *   A user object if a user with that mail address exists, otherwise an object
 *   with the properties mail and uid (set to 0).
 *
 * @ingroup subscriber
 */
function simplenews_load_user_by_mail($mail) {
  $account = user_load_by_mail($mail);
  if ($account === FALSE) {

    // Construct anonymous user since we don't have a user that matches that e-mail.
    $account = new stdClass();
    $account->uid = 0;
    $account->mail = $mail;
  }
  return $account;
}

/**
 * Subscribe a user to a newsletter or send a confirmation mail.
 *
 * The $confirm parameter determines the action:
 *   FALSE = The user is subscribed
 *   TRUE  = User receives an email to verify the address and complete the subscription
 * A new subscription account is created when the user is subscribed to the first newsletter
 *
 * @param string $mail
 *   The email address to subscribe to the newsletter.
 * @param integer $newsletter_id
 *   The newsletter ID.
 * @param boolean $confirm
 *   TRUE = send confirmation mail; FALSE = subscribe immediate to the newsletter
 * @param string $preferred_language
 *   The language code (i.e. 'en', 'nl') of the user preferred language.
 *   Use '' for the site default language.
 *   Use NULL for the language of the current page.
 * @param string $source
 *   Indication for source of subscription. Simplenews uses these sources:
 *    website: via any website form (with or without confirmation email)
 *    mass subscribe: mass admin UI
 *    mass unsubscribe: mass admin UI
 *    action: Drupal actions
 *
 * @ingroup subscription
 */
function simplenews_subscribe($mail, $newsletter_id, $confirm = TRUE, $source = 'unknown', $preferred_language = NULL) {
  global $language;

  // Get current subscriptions if any.
  $subscriber = simplenews_subscriber_load_by_mail($mail);

  // If user is not subscribed to ANY newsletter, create a subscription account
  if (!$subscriber) {

    // To subscribe a user:
    //   - Fetch the users uid.
    //   - Determine the user preferred language.
    //   - Add the user to the database.
    //   - Get the full subscription object based on the mail address.
    // Note that step 3 gets subscription data based on mail address because the uid can be 0 (for anonymous users)
    $account = simplenews_load_user_by_mail($mail);

    // If the site is multilingual:
    //  - Anonymous users are subscribed with their preferred language
    //    equal to the language of the current page.
    //  - Registered users will be subscribed with their default language as
    //    set in their account settings.
    // By default the preferred language is not set.
    if (variable_get('language_count', 1) > 1) {
      if ($account->uid) {
        $preferred_language = $account->language;
      }
      else {
        $preferred_language = isset($preferred_language) ? $preferred_language : $language->language;
      }
    }
    else {
      $preferred_language = '';
    }
    $subscriber = entity_create('simplenews_subscriber', array());
    $subscriber->mail = $mail;
    $subscriber->uid = $account->uid;
    $subscriber->language = $preferred_language;
    $subscriber->activated = 1;
    simplenews_subscriber_save($subscriber);
  }
  if ($confirm) {

    // Create an unconfirmed subscription object if it doesn't exist yet.
    if (!isset($subscriber->newsletter_ids[$newsletter_id])) {
      $subscription = new stdClass();
      $subscription->snid = $subscriber->snid;
      $subscription->newsletter_id = $newsletter_id;
      $subscription->status = SIMPLENEWS_SUBSCRIPTION_STATUS_UNCONFIRMED;
      $subscription->timestamp = REQUEST_TIME;
      $subscription->source = $source;
      simplenews_subscription_save($subscription);
      $subscriber->newsletter_subscription[$newsletter_id] = $subscription;
    }
    simplenews_confirmation_send('subscribe', $subscriber, simplenews_newsletter_load($newsletter_id));
  }
  elseif (!isset($subscriber->newsletter_ids[$newsletter_id])) {

    // Subscribe the user if not already subscribed.
    // @todo rewrite if subscription object is loaded in $subscriber->newsletter_ids[$newsletter_id]
    $subscription = new stdClass();
    $subscription->snid = $subscriber->snid;
    $subscription->newsletter_id = $newsletter_id;
    $subscription->status = SIMPLENEWS_SUBSCRIPTION_STATUS_SUBSCRIBED;
    $subscription->timestamp = REQUEST_TIME;
    $subscription->source = $source;
    simplenews_subscription_save($subscription);
    $subscriber->newsletter_subscription[$newsletter_id] = $subscription;
    $subscriber->newsletter_ids[$newsletter_id] = $newsletter_id;
    module_invoke_all('simplenews_subscribe', $subscriber, $subscription);
  }
  return TRUE;
}

/**
 * Unsubscribe a user from a mailing list or send a confirmation mail.
 *
 * The $confirm parameter determines the action:
 *   FALSE = The user is unsubscribed
 *   TRUE  = User receives an email to verify the address and complete the subscription cancellation
 *
 * @param $mail
 *   The email address to unsubscribe from the mailing list.
 * @param $newsletter_id
 *   The newsletter ID.
 * @param $confirm
 *   If TRUE, send a confirmation mail; if FALSE, unsubscribe immediately.
 * @param $source
 *   Indicates the unsubscribe source. Simplenews uses these sources:
 *   - website: Via any website form (with or without confirmation email).
 *   - mass subscribe: Mass admin UI.
 *   - mass unsubscribe: Mass admin UI.
 *   - action: Drupal actions.
 *
 * @ingroup subscription
 */
function simplenews_unsubscribe($mail, $newsletter_id, $confirm = TRUE, $source = 'unknown') {
  $subscriber = simplenews_subscriber_load_by_mail($mail);

  // The unlikely case that a user is unsubscribed from a non existing mailing list is logged
  if (!($newsletter = simplenews_newsletter_load($newsletter_id))) {
    watchdog('simplenews', 'Attempt to unsubscribe from non existing mailing list ID %id', array(
      '%id' => $newsletter_id,
    ), WATCHDOG_ERROR);
    return FALSE;
  }
  if ($confirm) {

    // Make sure the mail address is set.
    if (empty($subscriber)) {
      $subscriber = entity_create('simplenews_subscriber', array());
      $subscriber->mail = $mail;
    }
    simplenews_confirmation_send('unsubscribe', $subscriber, simplenews_newsletter_load($newsletter_id));
  }
  elseif (isset($subscriber->newsletter_ids[$newsletter_id])) {

    // Unsubscribe the user from the mailing list.
    $subscriber->newsletter_subscription[$newsletter_id]->status = SIMPLENEWS_SUBSCRIPTION_STATUS_UNSUBSCRIBED;
    $subscriber->newsletter_subscription[$newsletter_id]->timestamp = REQUEST_TIME;
    $subscriber->newsletter_subscription[$newsletter_id]->source = $source;
    simplenews_subscription_save($subscriber->newsletter_subscription[$newsletter_id]);
    unset($subscriber->newsletter_ids[$newsletter_id]);

    // Clear eventually existing mail spool rows for this subscriber.
    module_load_include('inc', 'simplenews', 'includes/simplenews.mail');
    simplenews_delete_spool(array(
      'snid' => $subscriber->snid,
      'newsletter_id' => $newsletter_id,
    ));
    module_invoke_all('simplenews_unsubscribe', $subscriber, $subscriber->newsletter_subscription[$newsletter_id]);
  }
  return TRUE;
}

/**
 * Check if the email address is subscribed to the given mailing list.
 *
 * @param string $mail
 *   The email address to be checkd.
 * @param integer $newsletter_id
 *   The mailing list id.
 *
 * @return boolean
 *   TRUE if the email address is subscribed; otherwise false.
 *
 * @ingroup subscription
 *
 * @todo Caching should be done in simplenews_load_user_by_mail().
 */
function simplenews_user_is_subscribed($mail, $newsletter_id) {
  $subscribed =& drupal_static(__FUNCTION__, array());
  if (!isset($subscribed[$mail][$newsletter_id])) {
    $subscriber = simplenews_subscriber_load_by_mail($mail);

    // Check that a subscriber was found, he is active and subscribed to the
    // requested newsletter_id.
    $subscribed[$mail][$newsletter_id] = $subscriber && $subscriber->activated && isset($subscriber->newsletter_ids[$newsletter_id]);
  }
  return $subscribed[$mail][$newsletter_id];
}

/**
 * Returns a list of active subscriptions for a given newsletter.
 *
 * WARNING: Use with caution - this might return a huge list
 *
 * @param $newsletter_id
 *   The newsletter id.
 *
 * @return
 *   An array keyed by the mail address, containing another array with the keys
 *   mail, uid, language, snid and status.
 *
 * @ingroup subscription
 */
function simplenews_get_subscriptions_by_list($newsletter_id) {
  $query = db_select('simplenews_subscriber', 'sn');
  $query
    ->innerJoin('simplenews_subscription', 'ss', 'ss.snid = sn.snid');
  $query
    ->fields('sn', array(
    'mail',
    'uid',
    'language',
    'snid',
  ))
    ->fields('ss', array(
    'status',
  ))
    ->condition('sn.activated', 1)
    ->condition('ss.newsletter_id', $newsletter_id)
    ->condition('ss.status', SIMPLENEWS_SUBSCRIPTION_STATUS_SUBSCRIBED);
  return $query
    ->execute()
    ->fetchAllAssoc('mail');
}

/**
 * Update subscriber objects in the database.
 *
 * @param $conditions
 *   Array of selection conditions. e.g. array('newsletter_id' => 5, 'snid' => 12).
 * @param $data
 *   Associative array of database fields to be updated.
 *
 * @todo Replace with simplenews_subscription_save() ?
 *
 * @ingroup subscription
 */
function simplenews_subscription_update($conditions = array(), $data) {
  $query = db_update('simplenews_subscription');
  foreach ($conditions as $key => $condition) {
    $query
      ->condition($key, $condition);
  }
  $query
    ->fields($data);
  $query
    ->execute();
}

/**
 * Save a subscription.
 *
 * @ingroup subscription
 */
function simplenews_subscription_save($subscription) {
  db_merge('simplenews_subscription')
    ->key(array(
    'newsletter_id' => $subscription->newsletter_id,
  ))
    ->key(array(
    'snid' => $subscription->snid,
  ))
    ->fields(array(
    'snid' => $subscription->snid,
    'newsletter_id' => $subscription->newsletter_id,
    'status' => $subscription->status,
    'timestamp' => $subscription->timestamp,
    'source' => $subscription->source,
  ))
    ->execute();
}

/**
 * Delete subscriptions.
 *
 * @param $conditions
 *   An associative array of conditions matching the records to be delete.
 *   Example: array('newsletter_id' => 5, 'snid' => 12)
 *   Delete the subscription of subscriber 12 to newsletter newsletter_id 5.
 *
 * @ingroup subscription
 */
function simplenews_subscription_delete($conditions = array()) {
  $query = db_delete('simplenews_subscription');
  foreach ($conditions as $key => $condition) {
    $query
      ->condition($key, $condition);
  }
  $query
    ->execute();
}

/**
 * Load a simplenews newsletter subscriber object.
 *
 * @param $snid
 *   Simplenews subscriber ID.
 *
 * @return SimplenewsSubscriber
 *   Newsletter subscriber entity, FALSE if subscriber does not exist.
 *
 * @ingroup subscriber
 */
function simplenews_subscriber_load($snid) {
  $subscribers = simplenews_subscriber_load_multiple(array(
    $snid,
  ));
  return $subscribers ? reset($subscribers) : FALSE;
}

/**
 * Load a simplenews newsletter subscriber object.
 *
 * @param $mail
 *   Subscriber e-mail address.
 *
 * @return SimplenewsSubscriber
 *   Newsletter subscriber object, FALSE if subscriber does not exist.
 *
 * @ingroup subscriber
 */
function simplenews_subscriber_load_by_mail($mail) {
  $subscribers = simplenews_subscriber_load_multiple(FALSE, array(
    'mail' => $mail,
  ));
  return $subscribers ? reset($subscribers) : FALSE;
}

/**
 * Load a simplenews newsletter subscriber object.
 *
 * @param $uid
 *   Subscriber user id.
 *
 * @return SimplenewsSubscriber
 *   Newsletter subscriber entity, FALSE if subscriber does not exist.
 *
 * @ingroup subscriber
 */
function simplenews_subscriber_load_by_uid($uid) {
  $subscribers = simplenews_subscriber_load_multiple(array(), array(
    'uid' => $uid,
  ));
  return $subscribers ? reset($subscribers) : FALSE;
}

/**
 * Load a simplenews subscriber using the hash.
 *
 * @param $hash
 *   A hash generated by simplenews_generate_hash().
 *
 * @return SimplenewsSubscriber
 *   Simplenews subscriber entity, FALSE if the hash is invalid or the
 *   corresponding subscriber does not exist.
 */
function simplenews_subscriber_load_by_hash($hash) {

  // Attempt to detect invalid format.
  if (!preg_match('/.{10}[0-9]+t[0-9]+/', $hash)) {
    return FALSE;
  }
  $md5 = drupal_substr($hash, 0, 10);
  list($snid, $newsletter_id) = explode('t', drupal_substr($hash, 10));
  $subscriber = simplenews_subscriber_load($snid);
  if (!$subscriber) {
    return FALSE;
  }

  // Check the hash if the comparison fails, return a not found error.
  if ($md5 != drupal_substr(md5($subscriber->mail . simplenews_private_key()), 0, 10)) {
    return FALSE;
  }
  return $subscriber;
}

/**
 * Loads simplenews subscriber objects.
 *
 * @param $snids
 *   (Optional) Array of subscriber ID's.
 * @param $conditions
 *   (Optional) Array of query conditions.
 *
 * @return
 *   Array of subscriber objects that match the given ID's/conditions.
 *
 * @ingroup subscriber
 */
function simplenews_subscriber_load_multiple($snids = array(), $conditions = array()) {
  return entity_load('simplenews_subscriber', $snids, $conditions);
}

/**
 * Store subscriber object in the database.
 *
 * @param SimplenewsSubscriber $subscriber
 *   The subscriber entity that should be saved.
 *
 * @ingroup subscriber
 */
function simplenews_subscriber_save($subscriber) {
  entity_save('simplenews_subscriber', $subscriber);
}

/**
 * Delete subscriber and corresponding subscriptions from the database.
 *
 * @param $snid
 *   Simplenews subscriber id.
 *
 * @ingroup subscriber
 */
function simplenews_subscriber_delete($snid) {
  entity_delete('simplenews_subscriber', $snid);
}

/**
 * Delete subscribers and corresponding subscriptions from the database.
 *
 * @param $snids
 *   Simplenews subscriber ids
 *
 * @ingroup subscriber
 */
function simplenews_subscriber_delete_multiple(array $snids) {
  entity_delete_multiple('simplenews_subscriber', $snids);
}

/**
 * Create a list of recent newsletters issues.
 *
 * @param integer $newsletter_id
 *   The newsletter id.
 * @param integer $count
 *   The number of newsletters.
 *
 * @ingroup issue
 *
 * @todo Replace this list by a View.
 */
function simplenews_recent_newsletters($newsletter_id, $count = 5) {
  $query = new EntityFieldQuery();
  $result = $query
    ->entityCondition('entity_type', 'node')
    ->propertyCondition('status', NODE_PUBLISHED)
    ->fieldCondition(variable_get('simplenews_newsletter_field', 'simplenews_newsletter'), 'target_id', $newsletter_id)
    ->fieldCondition(variable_get('simplenews_issue_status_field', 'simplenews_issue_status'), 'value', SIMPLENEWS_STATUS_SEND_NOT, '<>')
    ->propertyOrderBy('created', 'DESC')
    ->range(0, $count)
    ->execute();
  $titles = array();
  if (!empty($result['node'])) {
    foreach (node_load_multiple(array_keys($result['node'])) as $item) {
      $titles[$item->nid]['data'] = l($item->title, 'node/' . $item->nid);
    }
  }
  return $titles;
}

/**
 * Implements hook_mail().
 *
 * Send simplenews mails using drupal mail API.
 *
 * @param $key
 *   Must be one of: node, test, subscribe, unsubscribe.
 * @param $message
 *   The message array, containing at least the following keys:
 *   - from
 *   - headers: An array containing at least a 'From' key.
 *   - language: The preferred message language.
 * @param array $params
 *   The parameter array, containing the following keys:
 *   - simplenews_source: An implementation of SimplenewsSourceInterface which
 *     provides the necessary information to build the newsletter mail.
 *  @see drupal_mail()
 */
function simplenews_mail($key, &$message, $params) {
  module_load_include('inc', 'simplenews', 'includes/simplenews.mail');
  switch ($key) {
    case 'node':
    case 'test':
      simplenews_build_newsletter_mail($message, $params['simplenews_source']);
      break;
    case 'subscribe':
      simplenews_build_subscribe_mail($message, $params);
      break;
      break;
    case 'subscribe_combined':
      simplenews_build_combined_mail($message, $params);
      break;
    case 'unsubscribe':
      simplenews_build_unsubscribe_mail($message, $params);
      break;
  }

  // Debug message to check for outgoing emails messages.
  // Debug message of node and test emails is set in simplenews_mail_mail().
  if (variable_get('simplenews_debug', FALSE) && $key != 'node' && $key != 'test') {
    watchdog('simplenews', 'Outgoing email. Message type: %type<br />Subject: %subject<br />Recipient: %to', array(
      '%type' => $key,
      '%to' => $message['to'],
      '%subject' => $message['subject'],
    ), WATCHDOG_DEBUG);
  }
}

/**
 * Implements hook_views_api().
 */
function simplenews_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'simplenews') . '/includes/views',
  );
}

/**
 * Implements hook_ctools_plugin_type() to inform CTools about the recipients
 * plugin.
 */
function simplenews_ctools_plugin_type() {
  $plugins['recipient_handlers'] = array(
    'defaults' => array(
      'class' => 'SimplenewsRecipientsHandlerBase',
      'settings' => array(),
    ),
  );
  return $plugins;
}

/**
 * Build a recipient handler class from a given plugin id and settings array.
 *
 * @param $handler
 *   The id of the simplenews_recipient_handler plugin to use.
 * @param $settings
 *   An array of settings to pass to the handler class.
 *
 * @return SimplenewsRecipientHandlerInterface
 *   A constructed SimplenewsRecipientHandler class.
 *
 * @throws Exception if the handler class does not exist.
 */
function simplenews_get_recipient_handler($newsletter, $handler, $settings = array()) {
  ctools_include('plugins');
  $handler = ctools_get_plugins('simplenews', 'recipient_handlers', $handler);
  $class = $handler['class'];
  if (!class_exists($class)) {
    throw new Exception(t('Recipient Handler Class @class does not exist.', array(
      '@class' => $class,
    )));
  }
  return new $class($newsletter, $handler, $settings);
}

/**
 * Implements hook_ctools_plugin_directory().
 */
function simplenews_ctools_plugin_directory($owner, $plugin_type) {
  if ($owner == 'simplenews' && $plugin_type == 'recipient_handlers') {
    return 'plugins/recipient_handlers';
  }
}

/**
 * Get a simplenews newsletter entity object.
 *
 * @param $newsletter_id
 *   Simplenews newsletter ID.
 *
 * @return SimplenewsNewsletter
 *   Newsletter entity object, FALSE if newsletter does not exist.
 *
 * @ingroup newsletter
 */
function simplenews_newsletter_load($newsletter_id, $reset = FALSE) {
  if (!is_numeric($newsletter_id)) {
    return FALSE;
  }
  $newsletter = simplenews_newsletter_load_multiple(array(
    $newsletter_id,
  ), array(), $reset);
  return $newsletter ? reset($newsletter) : FALSE;
}

/**
 * Get list of simplenews categories with translated names.
 *
 * @todo Maybe refactor this method to simplenews_newsletter_name_list.
 *
 * @return
 *   array of newsletter names. Translated if required.
 *
 * @ingroup newsletter.
 */
function simplenews_newsletter_list() {
  $newsletters = array();
  foreach (simplenews_newsletter_get_all() as $id => $newsletter) {
    $newsletters[$id] = check_plain($newsletter->name);
  }
  return $newsletters;
}

/**
 * Loads all visible newsletters.
 *
 * Does not include newsletters with the opt-out/opt-in setting set to hidden.
 * It is possible to apply additional conditions.
 *
 * @param $conditions Aditional contitions.
 * @return array Filtered newsletter entities.
 *
 * @ingroup newsletter
 */
function simplenews_newsletter_get_visible($conditions = array()) {
  $query = new EntityFieldQuery();
  $query
    ->entityCondition('entity_type', 'simplenews_newsletter')
    ->propertyCondition('opt_inout', SIMPLENEWS_OPT_INOUT_HIDDEN, '<>')
    ->propertyOrderBy('weight');
  foreach ($conditions as $key => $value) {
    $query
      ->propertyCondition($key, $value);
  }
  $result = $query
    ->execute();
  return isset($result['simplenews_newsletter']) ? simplenews_newsletter_load_multiple(array_keys($result['simplenews_newsletter'])) : NULL;
}

/**
 * Loads all visible newsletters.
 *
 * Does not include newsletters with the opt-out/opt-in setting set to hidden.
 *
 * @return array All newsletter entities.
 *
 * @ingroup newsletter
 */
function simplenews_newsletter_get_all() {
  $query = new EntityFieldQuery();
  $result = $query
    ->entityCondition('entity_type', 'simplenews_newsletter')
    ->propertyOrderBy('weight')
    ->execute();
  return isset($result['simplenews_newsletter']) ? simplenews_newsletter_load_multiple(array_keys($result['simplenews_newsletter'])) : NULL;
}

/**
 * Load one or many newsletter entites from database.
 *
 * @param array $ids List of ids to be loaded or empty array for all.
 * @param array $conditions
 * @param boolean $reset
 * @return type
 *
 * @ingroup newsletter
 */
function simplenews_newsletter_load_multiple($ids = array(), $conditions = array(), $reset = FALSE) {
  return entity_load('simplenews_newsletter', $ids, $conditions, $reset);
}

/**
 * Store newsletter in the database.
 *
 * @param $newsletter
 *   Newsletter entity object
 *
 * @ingroup newsletter
 */
function simplenews_newsletter_save($newsletter) {
  return entity_save('simplenews_newsletter', $newsletter);
}

/**
 * Delete one or many newsletter from the database.
 *
 * @param $id
 *   Simplenews newsletter ID or Array of Simplenews newsletter IDs.
 *
 * @ingroup newsletter
 */
function simplenews_newsletter_delete($id) {
  entity_delete('simplenews_newsletter', $id);
}

/**
 * Create a 32 character identifier.
 */
function simplenews_private_key() {
  $key = variable_get('simplenews_private_key', FALSE);
  if (!$key) {

    //  This will create a 32 character identifier (a 128 bit hex number) that is extremely difficult to predict
    $key = md5(uniqid(rand()));
    variable_set('simplenews_private_key', $key);
  }
  return $key;
}

/**
 * Implements hook_help().
 *
 * @todo Rewrite help text to match the new terminology, the new data architecture, the new admin pages, the news node-form interface.
 */
function simplenews_help($path, $arg) {
  switch ($path) {
    case 'admin/help#simplenews':
      $help = "<p>" . t('Simplenews publishes and sends newsletters to lists of subscribers. Both anonymous and authenticated users can opt-in to different mailing lists.') . "</p>\n";
      $help .= "<p>" . t('Simplenews uses nodes for <strong>newsletter issues</strong>. Newsletter issues are grouped in a <strong>newsletter</strong>. Enabled Node types are selectable. A newsletter is send to all email addresses which are subscribed to the newsletter. Newsletter issues can be sent only once. Large mailings should be sent by cron to balance the mailserver load.') . "</p>\n";
      $help .= "<p>" . t('Simplenews adds elements to the newsletter node add/edit form to manage newsletter format and sending of the newsletter issue. A newsletter issue can be sent for test before sending officially.') . "</p>\n";
      $help .= "<p>" . t('Both anonymous and authenticated users can <strong>opt-in and opt-out</strong> to a newsletter. A confirmation message is sent to anonymous users when they (un)subscribe. Users can (un)subscribe using a form and a block. A <strong>subscription block</strong> is available for each newsletter offering a subscription form, a link to recent newsletters and RSS feed. Email addresses can also be imported and exported via the subscription administration pages.') . "</p>\n";
      $help .= "<h2>" . t('Configuration') . "</h2>\n";
      $help .= '<ul>';
      if (user_access('administer permissions')) {
        $help .= '<li>' . l(t('Configure permissions'), 'admin/people/permissions', array(
          'fragment' => 'module-simplenews',
        )) . "</li>\n";
      }
      if (user_access('administer simplenews settings')) {
        $help .= '<li>' . l(t('Configure Simplenews'), 'admin/config/services/simplenews/settings') . "</li>\n";
      }
      if (user_access('administer blocks')) {
        $help .= '<li>' . t('Enable a newsletter <a href="@admin_blocks">subscription block</a>.', array(
          '@admin_blocks' => url('admin/structure/block'),
        )) . "</li>\n";
      }
      if (user_access('administer simplenews settings')) {
        $help .= '<li>' . t('Manage your <a href="@newsletters">newsletters</a>, <a href="@sent">sent newsletters</a> and <a href="@subscriptions">subscriptions</a>.', array(
          '@newsletters' => url('admin/config/services/simplenews'),
          '@sent' => url('admin/content/simplenews'),
          '@subscriptions' => url('admin/people/simplenews'),
        )) . "</li>\n";
      }
      $help .= '</ul>';
      $help .= "<p>" . t('For more information, see the online handbook entry for <a href="@handbook">Simplenews</a>.', array(
        '@handbook',
        'http://drupal.org/node/197057',
      )) . "</p>\n";
      return $help;
    case 'node/add/simplenews':
      $help = '<ul>';
      $help .= '<li>' . t('Add this newsletter issue to a newsletter by selecting a newsletter from the select list. To send this newsletter issue, first save the node, then use the "Newsletter" tab.') . "</li>\n";
      if (user_access('administer simplenews settings')) {
        $help .= '<li>' . t('Set default send options at <a href="@configuration">Administration > Configuration > Web services > Newsletters</a>.', array(
          '@configuration' => url('admin/config/services/simplenews'),
        )) . "</li>\n";
      }
      if (user_access('administer newsletters')) {
        $help .= '<li>' . t('Set newsletter specific options at <a href="@configuration">Administration > Content > Newsletters</a>.', array(
          '@configuration' => url('admin/content/simplenews'),
        )) . "</li>\n";
      }
      $help .= '</ul>';
      return $help;
    case 'admin/config/services/simplenews/newsletter':
      $help = '<ul>';
      $help .= '<li>' . t('These settings are default to all newsletters. Newsletter specific settings can be found at the <a href="@page">newsletter\'s settings page</a>.', array(
        '@page' => url('admin/config/services/simplenews'),
      )) . "</li>\n";
      $help .= '<li>' . t('Install <a href="!mime_mail_url">Mime Mail</a> or <a href="!html_mail_url">HTML Mail</a> to send HTML emails or emails with attachments (both plain text and HTML).', array(
        '!mime_mail_url' => 'http://drupal.org/project/mimemail',
        '!html_mail_url' => 'http://drupal.org/project/htmlmail',
      )) . "</li>\n";
      $help .= '</ul>';
      return $help;
    case 'admin/config/services/simplenews':
      $help = '<p>' . t('Newsletter allow you to send periodic e-mails to subscribers. See <a href="!manage_subscribers">Newsletter subscriptions</a> for a listing of the subscribers', array(
        '!manage_subscribers' => url('admin/people/simplenews'),
      ));
      return $help;
    case 'admin/config/services/simplenews/add':
      $help = '<p>' . t('You can create different newsletters (or subjects) to categorize your news (e.g. Cats news, Dogs news, ...).') . "</p>\n";
      return $help;
    case 'admin/structure/types/manage/simplenews/display':
      $help = '<p>' . t("'Plain' display settings apply to the content of emails send in plain text format. 'HTML' display settings apply to both HTML and plain text alternative content of emails send in HTML format.") . "</p>\n";
      return $help;
  }
}

/**
 * Generates the hash key used for subscribe/unsubscribe link.
 */
function simplenews_generate_hash($mail, $action = '', $timestamp = REQUEST_TIME) {
  $data = $mail . drupal_get_private_key() . $action . $timestamp;
  return drupal_hash_base64($data);
}

/**
 * Generate a hash key in the old format.
 */
function simplenews_generate_old_hash($mail, $snid, $newsletter_id) {
  return drupal_substr(md5($mail . simplenews_private_key()), 0, 10) . $snid . 't' . $newsletter_id;
}

/**
 * Returns simplenews format options.
 */
function simplenews_format_options() {
  return array(
    'plain' => t('Plain'),
    'html' => t('HTML'),
  );
}

/**
 * Function to provide the various simplenews mail priorities for newsletters.
 */
function simplenews_get_priority() {
  return array(
    SIMPLENEWS_PRIORITY_NONE => t('none'),
    SIMPLENEWS_PRIORITY_HIGHEST => t('highest'),
    SIMPLENEWS_PRIORITY_HIGH => t('high'),
    SIMPLENEWS_PRIORITY_NORMAL => t('normal'),
    SIMPLENEWS_PRIORITY_LOW => t('low'),
    SIMPLENEWS_PRIORITY_LOWEST => t('lowest'),
  );
}

/**
 * Returns a list of options for the new account settings.
 */
function simplenews_new_account_options() {
  return array(
    'none' => t('None'),
    'on' => t('Default on'),
    'off' => t('Default off'),
    'silent' => t('Silent'),
  );
}

/**
 * Returns the available options for the opt_inout newsletter property.
 */
function simplenews_opt_inout_options() {
  return array(
    SIMPLENEWS_OPT_INOUT_HIDDEN => t('Hidden'),
    SIMPLENEWS_OPT_INOUT_SINGLE => t('Single'),
    SIMPLENEWS_OPT_INOUT_DOUBLE => t('Double'),
  );
}

/**
 * Implements hook_variable_info().
 */
function simplenews_variable_info($options) {
  $variable['simplenews_confirm_subscribe_subject'] = array(
    'title' => t('Simplenews confirmation subject'),
    'localize' => TRUE,
  );
  $variable['simplenews_confirm_subscribe_unsubscribed'] = array(
    'title' => t('Simplenews subscription confirmation body'),
    'localize' => TRUE,
  );
  $variable['simplenews_confirm_subscribe_subscribed'] = array(
    'title' => t('Simplenews subscribed confirmation body (already subscribed)'),
    'localize' => TRUE,
  );
  $variable['simplenews_confirm_unsubscribe_subscribed'] = array(
    'title' => t('Simplenews unsubscription confirmation body'),
    'localize' => TRUE,
  );
  $variable['simplenews_confirm_unsubscribe_unsubscribed'] = array(
    'title' => t('Simplenews unsubscription confirmation body (not subscribed)'),
    'localize' => TRUE,
  );
  $variable['simplenews_confirm_combined_subject'] = array(
    'title' => t('Simplenews combined confirmation subject'),
    'localize' => TRUE,
  );
  $variable['simplenews_confirm_combined_body'] = array(
    'title' => t('Simplenews combined confirmation body'),
    'localize' => TRUE,
  );
  $variable['simplenews_confirm_combined_body_unchanged'] = array(
    'title' => t('Simplenews combined confirmation body when there are no changes.'),
    'localize' => TRUE,
  );
  $variable['simplenews_confirm_combined_line_subscribe_unsubscribed'] = array(
    'title' => t('Simplenews combined confirmation subscribe line'),
    'localize' => TRUE,
  );
  $variable['simplenews_confirm_combined_line_subscribe_subscribed'] = array(
    'title' => t('Simplenews combined confirmation already subscribed line'),
    'localize' => TRUE,
  );
  $variable['simplenews_confirm_combined_line_unsubscribe_subscribed'] = array(
    'title' => t('Simplenews combined confirmation unsubscribe line'),
    'localize' => TRUE,
  );
  $variable['simplenews_confirm_combined_line_unsubscribe_unsubscribed'] = array(
    'title' => t('Simplenews combined confirmation not subscribed line'),
    'localize' => TRUE,
  );
  return $variable;
}

/**
 * Generate default and custom subscription confirmation email text.
 *
 * @param string $key
 *   Text identification key.
 *
 * @return
 *   Invitation text.
 *
 */
function simplenews_subscription_confirmation_text($key) {
  $text = variable_get('simplenews_confirm_' . $key, FALSE);

  // If administrator did not change the text, the variable is empty.
  // We get the default here.
  if (!$text) {
    switch ($key) {
      case 'subscribe_unsubscribed':
        $text = t("We have received a request to subscribe [simplenews-subscriber:mail] to the [simplenews-newsletter:name] newsletter on [site:name] website at [site:url]. To confirm please use the link below.\n\n[simplenews-subscriber:subscribe-url]");
        break;
      case 'subscribe_subscribed':
        $text = t("We have received a request to subscribe [simplenews-subscriber:mail] to the [simplenews-newsletter:name]  newsletter on [site:name] website at [site:url]. However, this email is already subscribed to this newsletter. If you intended to unsubscribe please visit our site: [site:url]");
        break;
      case 'unsubscribe_subscribed':
        $text = t("We have received a request to remove the [simplenews-subscriber:mail] from the [simplenews-newsletter:name] mailing list on [site:name] website at [site:url]. To confirm please use the link below.\n\n[simplenews-subscriber:unsubscribe-url]");
        break;
      case 'unsubscribe_unsubscribed':
        $text = t("We have received a request to remove the [simplenews-subscriber:mail] from the [simplenews-newsletter:name] mailing list on [site:name] website at [site:url]. However, this email is not subscribed to this mailing list. If you intended to subscribe please visit our site at [site:url]");
        break;
      case 'subscribe_subject':
        $text = t("Confirmation for [simplenews-newsletter:name] from [site:name]");
        break;
      case 'combined_subject':
        $text = t('Confirmation for [site:name]');
        break;
      case 'combined_body':
        $text = t("We have received a request for the following subscription changes for [simplenews-subscriber:mail] at [site:url]:\n\n[changes-list]\n\nTo confirm please use the link below.\n\n[simplenews-subscriber:combined-url]");
        break;
      case 'combined_body_unchanged':
        $text = t("We have received a request for the following subscription changes for [simplenews-subscriber:mail] at [site:url]:\n\n[changes-list]\n\nNo confirmation necessary because all requested changes equal the current state.");
        break;
      case 'combined_line_subscribe_unsubscribed':
        $text = t('Subscribe to [simplenews-newsletter:name]');
        break;
      case 'combined_line_subscribe_subscribed':
        $text = t('Already subscribed to [simplenews-newsletter:name]');
        break;
      case 'combined_line_unsubscribe_subscribed':
        $text = t('Unsubscribe from [simplenews-newsletter:name]');
        break;
      case 'combined_line_unsubscribe_unsubscribed':
        $text = t('Already unsubscribed from [simplenews-newsletter:name]');
        break;
    }
  }
  return $text;
}

/**
 * Implements hook_theme().
 *
 */
function simplenews_theme() {
  $path = drupal_get_path('module', 'simplenews');
  return array(
    'simplenews_multi_block' => array(
      'template' => 'simplenews-multi-block',
      'arguments' => array(),
      'path' => $path . '/theme',
    ),
    'simplenews_admin_categories' => array(
      'render element' => 'form',
    ),
    'simplenews_block' => array(
      'render element' => 'newsletter_id',
      'template' => 'simplenews-block',
      'pattern' => 'simplenews_block__',
      'path' => $path . '/theme',
    ),
    'simplenews_status' => array(
      'file' => 'simplenews.admin.inc',
      'path' => $path . '/includes',
      'variables' => array(
        'source' => NULL,
        'status' => NULL,
      ),
    ),
    'simplenews_newsletter_body' => array(
      'variables' => array(
        'build' => NULL,
        'newsletter' => NULL,
        'language' => NULL,
      ),
      'path' => $path . '/theme',
      'mail theme' => TRUE,
      'template' => 'simplenews-newsletter-body',
      'pattern' => 'simplenews-newsletter-body__',
    ),
    'simplenews_newsletter_footer' => array(
      'variables' => array(
        'build' => NULL,
        'newsletter' => NULL,
        'context' => NULL,
        'key' => NULL,
        'language' => NULL,
      ),
      'path' => $path . '/theme',
      'mail theme' => TRUE,
      'template' => 'simplenews-newsletter-footer',
      'pattern' => 'simplenews-newsletter-footer__',
    ),
    'simplenews_filter_form' => array(
      'render element' => 'form',
      'file' => 'simplenews.admin.inc',
      'path' => $path . '/includes',
    ),
    'simplenews_field' => array(
      'render element' => 'element',
    ),
  );
}

/**
 * Process variables to format the simplenews block.
 *
 * Collect data and apply access restrictions.
 *
 * @see simplenews-block.tpl.php
 *
 * @ingroup theming
 */
function template_preprocess_simplenews_block(&$variables) {
  global $user;
  $newsletter_id = $variables['newsletter_id'];
  $newsletter = simplenews_newsletter_load($newsletter_id);

  // Set default values in case of missing permission.
  $variables['form'] = '';
  $variables['subscription_link'] = '';
  $variables['newsletter_link'] = '';
  $variables['issue_list'] = '';
  $variables['rssfeed'] = '';

  // Block content variables
  $variables['message'] = check_plain(variable_get('simplenews_block_m_' . $newsletter_id, t('Stay informed on our latest news!')));
  if (user_access('subscribe to newsletters')) {
    module_load_include('inc', 'simplenews', 'simplenews.subscription');
    $variables['form'] = drupal_get_form('simplenews_block_form_' . $newsletter_id);
    $variables['subscription_link'] = l(t('Manage my subscriptions'), 'newsletter/subscriptions');
  }

  // @todo replace path
  $variables['newsletter_link'] = l(t('Previous issues'), 'newsletter/' . $newsletter_id);
  $recent = simplenews_recent_newsletters($newsletter_id, variable_get('simplenews_block_i_' . $newsletter_id, 5));
  $variables['issue_list'] = '';
  if ($recent) {
    $variables['issue_list'] = theme('item_list', array(
      'items' => $recent,
      'title' => t('Previous issues'),
      'type' => 'ul',
    ));
  }
  $variables['rssfeed'] = theme('feed_icon', array(
    'url' => 'newsletter/feed/' . $newsletter_id,
    'title' => t('@newsletter feed', array(
      '@newsletter' => $newsletter->name,
    )),
  ));

  // Block content control variables
  $variables['use_form'] = variable_get('simplenews_block_f_' . $newsletter_id, 1);
  $variables['use_issue_link'] = variable_get('simplenews_block_l_' . $newsletter_id, 1);
  $variables['use_issue_list'] = variable_get('simplenews_block_i_status_' . $newsletter_id, 0);
  $variables['use_rss'] = variable_get('simplenews_block_r_' . $newsletter_id, 1);

  // Additional variables
  $variables['subscribed'] = empty($user->uid) ? FALSE : simplenews_user_is_subscribed($user->mail, $newsletter_id) == TRUE;
  $variables['user'] = !empty($user->uid);
}

/**
 * Process variables for the multi subscription block.
 *
 * $variables are empty:
 *
 * @see simplenews-multi-block.tpl.php
 *
 * @ingroup theming
 */
function template_preprocess_simplenews_multi_block(&$variables) {

  // Block content variables
  $variables['message'] = check_plain(variable_get('simplenews_block_m_multiple', t('Select the newsletter(s) to which you want to subscribe or unsubscribe.')));
  $variables['form'] = drupal_get_form('simplenews_subscriptions_multi_block_form');
}

/**
 * @todo
 *
 * @see template_preprocess_field()
 * @see theme_simplenews_field()
 */
function template_preprocess_simplenews_field(&$variables, $hook) {
  $element = $variables['element'];
  $variables['label_hidden'] = $element['#label_display'] == 'hidden';
  $variables['label'] = $variables['label_hidden'] ? NULL : check_plain($element['#title']);
  $variables['items'] = array();
  foreach ($element['#items'] as $delta => $item) {
    if (!empty($element[$delta])) {
      $variables['items'][$delta] = $element[$delta];
    }
  }
  $variables['view_mode'] = $variables['element']['#view_mode'];

  // Add specific suggestions that can override the default implementation.
  $variables['theme_hook_suggestions'] = array(
    'simplenews_field__' . $element['#field_name'],
    'simplenews_field__' . $element['#view_mode'],
    'simplenews_field__' . $element['#field_name'] . '__' . $element['#view_mode'],
  );
}

/**
 * @todo
 *
 * @see theme_field()
 *
 * @ingroup theming
 */

// @todo make simplenews_field.tpl.php
function theme_simplenews_field($variables) {
  $output = '';
  switch ($variables['view_mode']) {
    case 'email_plain':
    case 'email_textalt':

      // Render the label, if it's not hidden.
      if (!$variables['label_hidden']) {
        $output .= $variables['label'] . ":\n";
      }

      // Render the items.
      foreach ($variables['items'] as $item) {
        $output .= drupal_render($item) . "\n";
      }

      // Add an extra line break at the end of the field.
      $output .= "\n";
      break;
    case 'email_html':
    default:

      // Render the label, if it's not hidden.
      if (!$variables['label_hidden']) {
        $output .= '<div class="field-label">' . $variables['label'] . ':&nbsp;</div>';
      }

      // Render the items.
      $output .= '<div class="field-items">';
      foreach ($variables['items'] as $delta => $item) {
        $classes = 'field-item ' . ($delta % 2 ? 'odd' : 'even');
        $output .= '<div class="' . $classes . '">' . drupal_render($item) . '</div>';
      }
      $output .= '</div>';

      // Render the top-level DIV.
      $output = '<div class="clearfix">' . $output . '</div>';
      break;
  }
  return $output;
}

/**
 * Process variables to format the simplenews newsletter body.
 *
 * @see simplenews-newsletter-body.tpl.php
 *
 * @ingroup theming
 */
function template_preprocess_simplenews_newsletter_body(&$variables) {

  // We don't want to include links and comments in the email.
  unset($variables['build']['links']);
  unset($variables['build']['comments']);
  $entity_type = $variables['build']['#entity_type'];
  $entity = !empty($variables['build']['#' . $entity_type]) ? $variables['build']['#' . $entity_type] : $variables['build']['#entity'];
  $theme = function_exists('mailsystem_get_mail_theme') ? mailsystem_get_mail_theme() : path_to_theme();
  $variables['simplenews_theme'] = drupal_get_path('theme', $theme);
  $variables['title'] = entity_label($entity_type, $entity);
  $variables['language'] = $variables['build']["#language"];
  $variables['view_mode'] = $variables['build']['#view_mode'];

  // Add specific suggestions that can override the default implementation.
  $variables['theme_hook_suggestions'] = array(
    'simplenews_newsletter_body__' . $variables['newsletter']->newsletter_id,
    'simplenews_newsletter_body__' . $variables['build']['#view_mode'],
    'simplenews_newsletter_body__' . $variables['newsletter']->newsletter_id . '__' . $variables['build']['#view_mode'],
  );
}

/**
 * Process variables to format the simplenews newsletter footer.
 *
 * @see simplenews-newsletter-footer.tpl.php
 *
 * @ingroup theming
 */
function template_preprocess_simplenews_newsletter_footer(&$variables) {

  // We don't want to include links and comments in the email.
  unset($variables['build']['links']);
  unset($variables['build']['comments']);
  $theme = function_exists('mailsystem_get_mail_theme') ? mailsystem_get_mail_theme() : path_to_theme();
  $variables['simplenews_theme'] = drupal_get_path('theme', $theme);
  $variables['unsubscribe_text'] = t('Unsubscribe from this newsletter', array(), array(
    'langcode' => $variables['language'],
  ));
  $variables['test_message'] = t('This is a test version of the newsletter.', array(), array(
    'langcode' => $variables['language'],
  ));
  $variables['view_mode'] = $variables['build']['#view_mode'];

  // Add specific suggestions that can override the default implementation.
  $variables['theme_hook_suggestions'] = array(
    'simplenews_newsletter_footer__' . $variables['newsletter']->newsletter_id,
    'simplenews_newsletter_footer__' . $variables['build']['#view_mode'],
    'simplenews_newsletter_footer__' . $variables['newsletter']->newsletter_id . '__' . $variables['build']['#view_mode'],
  );

  // Do not display the unsubscribe link by default for hidden categories.
  $variables['opt_out_hidden'] = $variables['newsletter']->opt_inout == 'hidden';
}

/**
 * Access callback; Acces to Newsletter tab page.
 */
function simplenews_node_tab_access($node) {
  return simplenews_check_node_types($node->type) && user_access('send newsletter');
}

/**
 * Update the sent status of a node.
 *
 * If the node part of a translation set, all corresponding translations are
 * updated as well.
 *
 * @param $node
 *   The node object to be updated.
 * @param $status
 *   The new status, defaults to SIMPLENEWS_STATUS_SEND_PENDING.
 *
 * @ingroup issue
 */
function simplenews_issue_update_sent_status($node, $status = SIMPLENEWS_STATUS_SEND_PENDING) {

  // When this node is selected for translation, all translation of this node
  // will be sent too.
  if (module_exists('translation') && translation_supported_type($node->type) && $node->tnid > 0) {
    if ($translations = translation_node_get_translations($node->tnid)) {
      foreach ($translations as $translation) {
        $tnode = node_load($translation->nid);
        simplenews_issue_status($tnode, SIMPLENEWS_STATUS_SEND_PENDING);
        node_save($tnode);
      }
    }
  }
  simplenews_issue_status($node, $status);
  node_save($node);
}

/**
 * Gets or sets the status of a newsletter issue.
 *
 * @param $node
 *   The issue to get or set the status.
 * @param $value
 *   (Optional) The status to be set or NULL to return the current value.
 * @return
 *   The newsletter status. Defaults to SIMPLENEWS_STATUS_SEND_NOT.
 *
 * @ingroup issue
 */
function simplenews_issue_status($node, $value = NULL) {
  $field = variable_get('simplenews_issue_status_field', 'simplenews_issue_status');
  if ($value) {
    return $node->{$field} = array(
      LANGUAGE_NONE => array(
        array(
          'value' => $value,
        ),
      ),
    );
  }
  elseif ($status = field_get_items('node', $node, $field)) {
    return $status[0]['value'];
  }
  return SIMPLENEWS_STATUS_SEND_NOT;
}

/**
 * Gets or sets the sent count of a newsletter issue.
 *
 * @param $entity_type
 *   Then entity_type of $entity.
 * @param $entity
 *   The issue to get or set the status.
 * @param $value
 *   (Optional) The sent count to be set or NULL to retrieve the current value.
 * @return
 *   The newsletter sent count. Defaults to 0.
 *
 * @ingroup issue
 */
function simplenews_issue_sent_count($entity_type, $entity, $value = NULL) {
  $field = variable_get('simplenews_sent_count_field', 'simplenews_sent_count');
  if ($value) {
    return $entity->{$field} = array(
      LANGUAGE_NONE => array(
        array(
          'value' => $value,
        ),
      ),
    );
  }
  elseif ($status = field_get_items($entity_type, $entity, $field)) {
    return $status[0]['value'];
  }
  return 0;
}

/**
 * Gets or sets the newsletter for an issue.
 *
 * @param $node
 *   The issue to get or set the status.
 * @param $value
 *   (Optional) The newsletter id to be set or NULL to retrieve the current value.
 * @return
 *   The newsletter id or 0 if not set.
 *
 * @ingroup issue
 */
function simplenews_issue_newsletter_id($node, $value = NULL) {
  $field = variable_get('simplenews_newsletter_field', 'simplenews_newsletter');
  if ($value) {
    return $node->{$field} = array(
      LANGUAGE_NONE => array(
        array(
          'target_id' => $value,
        ),
      ),
    );
  }
  elseif ($newsletter_id = field_get_items('node', $node, $field)) {
    return $newsletter_id[0]['target_id'];
  }
  return 0;
}

/**
 * Gets or sets the handler for an issue.
 *
 * @param $node
 *   The issue to get or set the status.
 * @param $handler
 *   (Optional) The recipient handler to be set or NULL to retrieve the current value.
 * @return
 *   The recipient handler name or FALSE if not set.
 *
 * @ingroup issue
 */
function simplenews_issue_handler($node, $handler = NULL) {
  $field = variable_get('simplenews_handler_field', 'simplenews_handler');
  if ($handler) {
    return $node->{$field} = array(
      LANGUAGE_NONE => array(
        array(
          'value' => $handler,
        ),
      ),
    );
  }
  elseif ($handler = field_get_items('node', $node, $field)) {
    return $handler[0]['value'];
  }
  return FALSE;
}

/**
 * Gets or sets the handler settings for an issue.
 *
 * @param $node
 *   The issue to get or set the status.
 * @param $settings
 *   (Optional) The recipient handler settings to be set or NULL to retrieve the current settings.
 * @return
 *   The recipient handler settings or an empty array if not set.
 *
 * @ingroup issue
 */
function simplenews_issue_handler_settings($node, $settings = NULL) {
  $field = variable_get('simplenews_handler_settings_field', 'simplenews_handler_settings');
  if ($settings) {
    return $node->{$field} = array(
      LANGUAGE_NONE => array(
        array(
          'value' => serialize($settings),
        ),
      ),
    );
  }
  elseif ($settings = field_get_items('node', $node, $field)) {
    return unserialize($settings[0]['value']);
  }
  return FALSE;
}

/**
 * Implements hook_field_extra_fields().
 */
function simplenews_field_extra_fields() {
  $return['user']['user'] = array(
    'display' => array(
      'simplenews' => array(
        'label' => 'Newsletters',
        'description' => t('Newsletter subscriptions of the user'),
        'weight' => 5,
      ),
    ),
  );
  return $return;
}

/**
 * Implements hook_node_access().
 *
 * Don't allow deletion when a newsletter is pending
 */
function simplenews_node_access($node, $op, $account) {
  if ($op == 'delete') {

    // Check if a newsletter is pending
    if (simplenews_issue_status($node) == SIMPLENEWS_STATUS_SEND_PENDING) {
      drupal_set_message(t('You can\'t delete this newsletter because it has not been sent to all its subscribers.'), 'warning');
      return NODE_ACCESS_DENY;
    }
  }
  return NODE_ACCESS_IGNORE;
}

/**
 * Implements hook_cronapi().
 *
 * As initiated by elysia_cron.
 * http://drupal.org/project/elysia_cron
 * */
function simplenews_cronapi($op) {
  switch ($op) {
    case 'list':
      return array(
        'simplenews_cron' => t('Process simplenews mail spool'),
      );
    case 'rule':
      return '5 * * * *';
    case 'execute':
      break;
  }
}

/**
 * Returns TRUE if the newsletter requires double opt in.
 *
 * @ingroup newsletter
 */
function simplenews_require_double_opt_in($newsletter_id, $account) {
  global $user;

  // If email belongs to the current registered user, don't send confirmation.
  // Other addresses receive a confirmation if double opt-in is selected.
  if (!empty($account->uid) && !empty($user->uid) && $account->uid == $user->uid) {
    return FALSE;
  }
  else {
    $newsletter = simplenews_newsletter_load($newsletter_id);
    return $newsletter->opt_inout == 'double';
  }
}

/**
 * Returns the available simplenews sources.
 *
 * @ingroup source
 */
function simplenews_get_source_caches() {
  $sources = module_invoke_all('simplenews_source_cache_info');
  drupal_alter('simplenews_source_cache_info', $sources);
  return $sources;
}

/**
 * Implements hook_simplenews_source_cache_info().
 *
 * @ingroup source
 */
function simplenews_simplenews_source_cache_info() {
  return array(
    'SimplenewsSourceCacheNone' => array(
      'label' => t('No caching'),
      'description' => t('This allows to theme each newsletter separately.'),
    ),
    'SimplenewsSourceCacheBuild' => array(
      'label' => t('Cached content source'),
      'description' => t('This caches the rendered content to be sent for multiple recipients. It is not possible to use subscriber specific theming but tokens can be used for personalization.'),
    ),
  );
}

/**
 * Impersonates another user.
 *
 * Each time this function is called, the active user is saved and $new_user
 * becomes the active user. Multiple calls to this function can be nested,
 * and session saving will be disabled until all impersonation attempts have
 * been reverted using user_revert_user().
 *
 * @todo: This function is a backport of http://drupal.org/node/287292. Switch
 * to that once available.
 *
 * @param $new_user
 *   User to impersonate, either a UID or a user object.
 *
 * @return
 *   Current user object.
 *
 * @see simplenews_revert_user()
 */
function simplenews_impersonate_user($new_user = NULL) {
  global $user;
  $user_original =& drupal_static(__FUNCTION__);
  if (!isset($new_user)) {
    if (isset($user_original) && !empty($user_original)) {

      // Restore the previous user from the stack.
      $user = array_pop($user_original);

      // Re-enable session saving if we are no longer impersonating a user.
      if (empty($user_original)) {
        drupal_save_session(TRUE);
      }
    }
  }
  else {

    // Push the original user onto the stack and prevent session saving.
    $user_original[] = $user;
    drupal_save_session(FALSE);
    if (is_numeric($new_user)) {
      $user = user_load($new_user);
    }
    else {
      $user = is_object($new_user) ? $new_user : (object) $new_user;
    }
  }
  return $user;
}

/**
 * Reverts to the previous user after impersonating.
 *
 * @return
 *   Current user.
 *
 * @see simplenews_impersonate_user()
 */
function simplenews_revert_user() {
  return simplenews_impersonate_user();
}

/**
 * Starts combining confirmation mails.
 *
 * If combining mails is enabled, it is mandatory to call
 * simplenews_send_combined_confirmation() after all subscription changes have
 * been applied or no mails will be sent.
 *
 * @param $collect
 *   TRUE to start combining mail conformations, FALSE to disable it.
 *
 * @return
 *   TRUE if combining has been started, FALSE otherwise.
 */
function simplenews_confirmation_combine($collect = NULL) {
  $static_collect =& drupal_static(__FUNCTION__, FALSE);
  if (isset($collect)) {
    $static_collect = $collect;
  }
  return $static_collect;
}

/**
 * Add a mail confirmation or fetch them.
 *
 * @param $action
 *   (Optional) The confirmation type, either subscribe or unsubscribe.
 * @param $subscriber
 *   (Optional) The subscriber object.
 * @param $newsletter
 *   (Optional) The newsletter object.
 *
 * @return
 *   If no arguments are passed in, an array of current confirmations, keyed by
 *   subscriber id, the value is an array with the keys action and newsletter_id.
 */
function simplenews_confirmation_add_combined($action = NULL, $subscriber = NULL, $newsletter = NULL) {
  $group =& drupal_static(__FUNCTION__, array());
  if (!empty($action)) {
    $group[$subscriber->mail][$newsletter->newsletter_id] = $action;
  }
  else {
    return $group;
  }
}

/**
 * Send collected confirmations.
 *
 * Depending on the settings, always sends a combined confirmation,
 * only when there are multiple changes for a subscriber or never.
 *
 * Calling this functions also resets the combine flag so that later
 * confirmations are sent separately. simplenews_combine_confirmations() needs
 * to be called again to re-enable combining.
 *
 * @return
 *   TRUE if any confirmation mails have been sent.
 *
 * @todo This function currently does not return information about which
 *       subscriber received a confirmation.
 */
function simplenews_confirmation_send_combined() {
  global $language;

  // Disable combining for further confirmations.
  simplenews_confirmation_combine(FALSE);
  $group = simplenews_confirmation_add_combined();
  foreach ($group as $mail => $changes) {
    module_load_include('inc', 'simplenews', 'includes/simplenews.mail');
    $params['from'] = _simplenews_set_from();
    $subscriber = simplenews_subscriber_load_by_mail($mail);
    if (!$subscriber) {
      $subscriber = new stdClass();
      $subscriber->mail = $mail;
      $subscriber->language = $language->language;
      $subscriber->newsletter_ids = array();
    }
    $params['context']['simplenews_subscriber'] = $subscriber;

    // Send multiple if there is more than one change for this subscriber
    // single otherwise.
    $use_combined = variable_get('simplenews_use_combined', 'multiple');
    $subscriber->changes = $changes;
    if (count($changes) > 1 && $use_combined != 'never' || $use_combined == 'always') {
      $key = 'subscribe_combined';
      drupal_mail('simplenews', $key, $subscriber->mail, $subscriber->language, $params, $params['from']['address']);
    }
    else {
      foreach ($changes as $newsletter_id => $key) {
        $params['context']['newsletter'] = simplenews_newsletter_load($newsletter_id);
        drupal_mail('simplenews', $key, $subscriber->mail, $subscriber->language, $params, $params['from']['address']);
      }
    }

    // Save the changes in the subscriber if there is a real subscriber object.
    if (!empty($subscriber->snid)) {
      simplenews_subscriber_save($subscriber);
    }
  }
  return !empty($group);
}

/**
 * Send a confirmation mail.
 *
 * Either sends a mail immediatly or collects them for a combined mail.
 *
 * @param $action
 *   The confirmation type, either subscribe or unsubscribe.
 * @param $subscriber
 *   The subscriber object.
 * @param $newsletter
 *   The newsletter object.
 */
function simplenews_confirmation_send($action, $subscriber, $newsletter) {

  // Check if confirmation should be sent immediatly or grouped.
  if (simplenews_confirmation_combine()) {
    simplenews_confirmation_add_combined($action, $subscriber, $newsletter);
  }
  else {

    // Send confirmation email to user to complete (un)subscription.
    // Confirmation mail is in the user preferred language which is by default the
    // language_default().
    module_load_include('inc', 'simplenews', 'includes/simplenews.mail');
    $params['from'] = _simplenews_set_from();
    $params['context']['newsletter'] = $newsletter;
    $params['context']['simplenews_subscriber'] = $subscriber;
    drupal_mail('simplenews', $action, $subscriber->mail, $subscriber->language, $params, $params['from']['address']);
  }
}

/**
 * Converts an array of subscription changes into descriptions.
 *
 * @param $subscriber
 *   Simplenews subscriber object.
 * @param $changes
 *   (Optional) Array of changes, each is an array with the keys action and newsletter_id.
 *   Defaults to $subscriber->changes, which contains the currently saved
 *   changes for the subscriber. No-op changes are removed from this array.
 * @param $langcode
 *   (Optional) Specify the language of the description strings, defaults to the
 *   current language.
 *
 * @return
 *   Array of description strings describing the changes.
 */
function simplenews_confirmation_get_changes_list($subscriber, &$changes = NULL, $langcode = NULL) {
  if (empty($langcode)) {
    global $language;
    $langcode = $language->language;
  }
  if (empty($changes)) {
    $changes = $subscriber->changes;
  }
  $changes_list = array();
  foreach ($changes as $newsletter_id => $action) {
    $subscribed = simplenews_user_is_subscribed($subscriber->mail, $newsletter_id);

    // Get text for each possible combination.
    if ($action == 'subscribe' && !$subscribed) {
      $line = simplenews_subscription_confirmation_text('combined_line_subscribe_unsubscribed', $langcode);
    }
    elseif ($action == 'subscribe' && $subscribed) {
      $line = simplenews_subscription_confirmation_text('combined_line_subscribe_subscribed', $langcode);
    }
    elseif ($action == 'unsubscribe' && !$subscribed) {
      $line = simplenews_subscription_confirmation_text('combined_line_unsubscribe_unsubscribed', $langcode);
    }
    elseif ($action == 'unsubscribe' && $subscribed) {
      $line = simplenews_subscription_confirmation_text('combined_line_unsubscribe_subscribed', $langcode);
    }
    $newsletter_context = array(
      'simplenews_subscriber' => $subscriber,
      'newsletter' => simplenews_newsletter_load($newsletter_id),
    );
    $changes_list[$newsletter_id] = token_replace($line, $newsletter_context, array(
      'sanitize' => FALSE,
    ));
  }
  return $changes_list;
}

/**
 * Implements hook_field_access().
 */
function simplenews_field_access($op, $field, $entity_type, $entity, $account) {

  // Deny access to the issue status and sent count fields, these are used for
  // data storage only.
  if (in_array($field['field_name'], array(
    'simplenews_issue_status',
    'simplenews_sent_count',
    'simplenews_handler_settings',
    'simplenews_handler',
  ))) {
    return FALSE;
  }
}

/**
 * Getter callback for simplenews_subscriptions property.
 *
 * The simplenews_subscriptions property is defined in simplenews.info.inc
 */
function simplenews_property_simplenews_subscriptions_getter_callback($item) {
  return $item->newsletter_ids;
}

Functions

Namesort descending Description
simplenews_admin_paths Implements hook_admin_paths().
simplenews_block_configure Implements hook_block_configure().
simplenews_block_info Implements hook_block_info().
simplenews_block_save Implements hook_block_save().
simplenews_block_view Implements hook_block_view().
simplenews_check_node_types Check if content type(s) is enabled for use as Simplenews newsletter.
simplenews_confirmation_add_combined Add a mail confirmation or fetch them.
simplenews_confirmation_combine Starts combining confirmation mails.
simplenews_confirmation_get_changes_list Converts an array of subscription changes into descriptions.
simplenews_confirmation_send Send a confirmation mail.
simplenews_confirmation_send_combined Send collected confirmations.
simplenews_cron Implements hook_cron().
simplenews_cronapi Implements hook_cronapi().
simplenews_ctools_plugin_directory Implements hook_ctools_plugin_directory().
simplenews_ctools_plugin_type Implements hook_ctools_plugin_type() to inform CTools about the recipients plugin.
simplenews_entity_info Implements hook_entity_info().
simplenews_entity_info_alter Implements hook_entity_info_alter().
simplenews_field_access Implements hook_field_access().
simplenews_field_extra_fields Implements hook_field_extra_fields().
simplenews_format_options Returns simplenews format options.
simplenews_forms Implements hook_forms().
simplenews_form_alter Implements hook_form_alter().
simplenews_form_node_type_form_alter Implements hook_form_FORM_ID_alter().
simplenews_form_user_profile_form_alter Implements hook_form_FORM_ID_alter().
simplenews_form_user_register_form_alter Implements hook_form_FORM_ID_alter().
simplenews_generate_hash Generates the hash key used for subscribe/unsubscribe link.
simplenews_generate_old_hash Generate a hash key in the old format.
simplenews_get_content_types Get all node types supported by Simplenews.
simplenews_get_newsletter_field Get the fieldname(s) from a content type that hold the newsletter.
simplenews_get_priority Function to provide the various simplenews mail priorities for newsletters.
simplenews_get_recipient_handler Build a recipient handler class from a given plugin id and settings array.
simplenews_get_source_caches Returns the available simplenews sources.
simplenews_get_subscriptions_by_list Returns a list of active subscriptions for a given newsletter.
simplenews_handler_field_allowed_values Returns the array of allowed values for the simplenews_newsletter_handler field.
simplenews_help Implements hook_help().
simplenews_impersonate_user Impersonates another user.
simplenews_init Implements hook_init().
simplenews_issue_fields_add Add fields for newsletter, status and sent count to a note type.
simplenews_issue_fields_remove Remove fields for newsletter, status and sent count to a note type.
simplenews_issue_handler Gets or sets the handler for an issue.
simplenews_issue_handler_field_add Add a field for the recipient handler choice.
simplenews_issue_handler_settings Gets or sets the handler settings for an issue.
simplenews_issue_handler_settings_field_add Add a field for the recipient handler settings.
simplenews_issue_newsletter_field_add Add field for newsletter
simplenews_issue_newsletter_id Gets or sets the newsletter for an issue.
simplenews_issue_sent_count Gets or sets the sent count of a newsletter issue.
simplenews_issue_sent_count_field_add Add field for status
simplenews_issue_status Gets or sets the status of a newsletter issue.
simplenews_issue_status_field_add Add field for status
simplenews_issue_update_sent_status Update the sent status of a node.
simplenews_load_user_by_mail Load a user or creates a dummy anonymous user.
simplenews_mail Implements hook_mail().
simplenews_menu Implements hook_menu().
simplenews_newsletter_access Entity access callback for simplenews newsletter entity.
simplenews_newsletter_delete Delete one or many newsletter from the database.
simplenews_newsletter_get_all Loads all visible newsletters.
simplenews_newsletter_get_visible Loads all visible newsletters.
simplenews_newsletter_list Get list of simplenews categories with translated names.
simplenews_newsletter_load Get a simplenews newsletter entity object.
simplenews_newsletter_load_multiple Load one or many newsletter entites from database.
simplenews_newsletter_save Store newsletter in the database.
simplenews_new_account_options Returns a list of options for the new account settings.
simplenews_node_access Implements hook_node_access().
simplenews_node_delete Implements hook_node_delete().
simplenews_node_tab_access Access callback; Acces to Newsletter tab page.
simplenews_node_type_delete Implements hook_node_type_delete().
simplenews_node_type_insert Implements hook_node_type_insert().
simplenews_node_type_update Implements hook_node_type_update().
simplenews_node_update Implements hook_node_update().
simplenews_node_view Implements hook_node_view().
simplenews_opt_inout_options Returns the available options for the opt_inout newsletter property.
simplenews_permission Implements hook_permission().
simplenews_private_key Create a 32 character identifier.
simplenews_property_simplenews_subscriptions_getter_callback Getter callback for simplenews_subscriptions property.
simplenews_recent_newsletters Create a list of recent newsletters issues.
simplenews_require_double_opt_in Returns TRUE if the newsletter requires double opt in.
simplenews_revert_user Reverts to the previous user after impersonating.
simplenews_simplenews_source_cache_info Implements hook_simplenews_source_cache_info().
simplenews_subscribe Subscribe a user to a newsletter or send a confirmation mail.
simplenews_subscriber_access Entity access callback for simplenews newsletter entity.
simplenews_subscriber_delete Delete subscriber and corresponding subscriptions from the database.
simplenews_subscriber_delete_multiple Delete subscribers and corresponding subscriptions from the database.
simplenews_subscriber_load Load a simplenews newsletter subscriber object.
simplenews_subscriber_load_by_hash Load a simplenews subscriber using the hash.
simplenews_subscriber_load_by_mail Load a simplenews newsletter subscriber object.
simplenews_subscriber_load_by_uid Load a simplenews newsletter subscriber object.
simplenews_subscriber_load_multiple Loads simplenews subscriber objects.
simplenews_subscriber_save Store subscriber object in the database.
simplenews_subscription_confirmation_text Generate default and custom subscription confirmation email text.
simplenews_subscription_delete Delete subscriptions.
simplenews_subscription_edit_access Access callback for user newsletter editing.
simplenews_subscription_save Save a subscription.
simplenews_subscription_update Update subscriber objects in the database.
simplenews_theme Implements hook_theme().
simplenews_unsubscribe Unsubscribe a user from a mailing list or send a confirmation mail.
simplenews_user_cancel Implements hook_user_cancel().
simplenews_user_categories Implements hook_user_categories().
simplenews_user_delete Implements hook_user_delete().
simplenews_user_insert Implements hook_user_insert().
simplenews_user_is_subscribed Check if the email address is subscribed to the given mailing list.
simplenews_user_login Implements hook_user_login().
simplenews_user_presave Implements hook_user_presave().
simplenews_user_view Implements hook_user_view().
simplenews_variable_info Implements hook_variable_info().
simplenews_views_api Implements hook_views_api().
template_preprocess_simplenews_block Process variables to format the simplenews block.
template_preprocess_simplenews_field @todo
template_preprocess_simplenews_multi_block Process variables for the multi subscription block.
template_preprocess_simplenews_newsletter_body Process variables to format the simplenews newsletter body.
template_preprocess_simplenews_newsletter_footer Process variables to format the simplenews newsletter footer.
theme_simplenews_field
_simplenews_node_form @todo

Constants