You are here

simplenews.module in Simplenews 6

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

File

simplenews.module
View source
<?php

/**
 * @defgroup simplenews
 * Enable nodes to be used as newsletter, manage subscriptions and sent
 * email newsletter to subscribers.
 */

/**
 * @file
 * Simplnews node handling, sent email, newsletter block and general hooks
 *
 * @ingroup simplenews
 */

// TODO: in Drupal 7 try to replace time() by $_SERVER[''REQUEST_TIME'] (requires php 5.1.0)

/**
 * 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_NONE', 0);
define('SIMPLENEWS_COMMAND_SEND_NOW', 1);
define('SIMPLENEWS_COMMAND_SEND_TEST', 2);

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

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

/**
 * 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);

/**
 * CAPTURE THE PHP MAX EXUCUTION TIME BEFORE drupal_cron_run() CHANGES THIS.
 * THIS IS A WORK AROUND FOR DRUPAL6.14 BUG. SEE http://drupal.org/node/584334
 */
define('SIMPLENEWS_MAX_EXECUTION_TIME', ini_get('max_execution_time'));

/**
 * Implementation of hook_perm().
 */
function simplenews_perm() {
  return array(
    'administer newsletters',
    'administer simplenews subscriptions',
    'administer simplenews settings',
    'send newsletter',
    'subscribe to newsletters',
  );
}

/**
 * Implementation of hook_init().
 */
function simplenews_init() {
  drupal_add_css(drupal_get_path('module', 'simplenews') . '/simplenews.css', 'module', 'all', TRUE);

  // Simplenews can not work without this variable.
  if (variable_get('simplenews_vid', '') == '') {
    drupal_set_message(t('Missing newsletter vocabulary. Please set a vocabulary at <a href="@settings">Simplenews settings</a>.', array(
      '@settings' => url('admin/settings/simplenews/general'),
    )), 'error');
  }
}

/**
 * Implementation of hook_menu().
 */
function simplenews_menu() {
  $items['admin/content/simplenews'] = array(
    'title' => 'Newsletters',
    'description' => 'Manage newsletters and subscriptions.',
    'type' => MENU_NORMAL_ITEM,
    'page callback' => 'system_admin_menu_block_page',
    'access callback' => 'simplenews_newsletter_access',
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
  );
  $items['admin/content/simplenews/sent'] = array(
    'title' => 'Sent issues',
    'description' => 'List of newsletter issues sent and pending.',
    'type' => MENU_NORMAL_ITEM,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_news',
    ),
    'access arguments' => array(
      'administer newsletters',
    ),
    'file' => 'simplenews.admin.inc',
    'weight' => -10,
  );
  $items['admin/content/simplenews/notsent'] = array(
    'title' => 'Draft issues',
    'description' => 'List of newsletter issues not sent.',
    'type' => MENU_NORMAL_ITEM,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_news',
      'notsent',
    ),
    'access arguments' => array(
      'administer newsletters',
    ),
    'file' => 'simplenews.admin.inc',
    'weight' => -9,
  );
  $items['admin/content/simplenews/types'] = array(
    'title' => 'Newsletters',
    'description' => 'List, add and edit newsletter series.',
    'type' => MENU_NORMAL_ITEM,
    'page callback' => 'simplenews_types_overview',
    'access arguments' => array(
      'administer newsletters',
    ),
    'file' => 'simplenews.admin.inc',
    'weight' => -8,
  );
  $items['admin/content/simplenews/types/edit/%'] = array(
    'title' => 'Newsletters',
    'type' => MENU_CALLBACK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_types_form',
      5,
    ),
    'access arguments' => array(
      'administer newsletters',
    ),
    'file' => 'simplenews.admin.inc',
  );
  $items['admin/content/simplenews/types/delete/%'] = array(
    'title' => 'Newsletters',
    'type' => MENU_CALLBACK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_types_delete',
      5,
    ),
    'access arguments' => array(
      'administer newsletters',
    ),
    'file' => 'simplenews.admin.inc',
  );
  $items['admin/content/simplenews/types/list'] = array(
    'title' => 'List newsletters',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/content/simplenews/types/add'] = array(
    'title' => 'Add newsletter',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_types_form',
    ),
    'access arguments' => array(
      'administer newsletters',
    ),
    'file' => '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' => 'simplenews.admin.inc',
  );
  $items['admin/content/simplenews/users'] = array(
    'title' => 'Subscriptions',
    'description' => 'Newsletter subscription management.',
    'type' => MENU_NORMAL_ITEM,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_subscription_admin',
    ),
    'access arguments' => array(
      'administer simplenews subscriptions',
    ),
    'file' => 'simplenews.admin.inc',
    'weight' => -7,
  );
  $items['admin/content/simplenews/users/edit/%'] = array(
    'title' => 'Subscriptions',
    'type' => MENU_CALLBACK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_users_form',
      5,
    ),
    'access arguments' => array(
      'administer simplenews subscriptions',
    ),
    'file' => 'simplenews.subscription.inc',
  );
  $items['admin/content/simplenews/users/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/content/simplenews/users/import'] = array(
    'title' => 'Mass subscribe',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_subscription_list_add',
    ),
    'access arguments' => array(
      'administer simplenews subscriptions',
    ),
    'file' => 'simplenews.admin.inc',
    'weight' => -9,
  );
  $items['admin/content/simplenews/users/export'] = array(
    'title' => 'Export',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_subscription_list_export',
    ),
    'access arguments' => array(
      'administer simplenews subscriptions',
    ),
    'file' => 'simplenews.admin.inc',
    'weight' => -7,
  );
  $items['admin/content/simplenews/users/unsubscribe'] = array(
    'title' => 'Mass unsubscribe',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_subscription_list_remove',
    ),
    'access arguments' => array(
      'administer simplenews subscriptions',
    ),
    'file' => 'simplenews.admin.inc',
    'weight' => -8,
  );
  $items['admin/settings/simplenews'] = array(
    'title' => 'Simplenews',
    'description' => 'Manage simplenews configuration.',
    'type' => MENU_NORMAL_ITEM,
    'page callback' => 'system_admin_menu_block_page',
    'access arguments' => array(
      'administer simplenews settings',
    ),
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
  );
  $items['admin/settings/simplenews/general'] = array(
    'title' => 'General',
    'description' => 'Simplenews content type and vocabulary settings.',
    'type' => MENU_NORMAL_ITEM,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_settings',
    ),
    'access arguments' => array(
      'administer simplenews settings',
    ),
    'file' => 'simplenews.admin.inc',
    'weight' => -10,
  );
  $items['admin/settings/simplenews/newsletter'] = array(
    'title' => 'Newsletter',
    'description' => 'Newsletter default settings and sender data.',
    'type' => MENU_NORMAL_ITEM,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_settings_newsletter',
    ),
    'access arguments' => array(
      'administer simplenews settings',
    ),
    'file' => 'simplenews.admin.inc',
    'weight' => -9,
  );
  $items['admin/settings/simplenews/subscription'] = array(
    'title' => 'Subscription',
    'description' => 'Subscription settings, opt-in/out confirmation email text.',
    'type' => MENU_NORMAL_ITEM,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_settings_subscription',
    ),
    'access arguments' => array(
      'administer simplenews settings',
    ),
    'file' => 'simplenews.admin.inc',
    'weight' => -8,
  );
  $items['admin/settings/simplenews/mail'] = array(
    'title' => 'Send mail',
    'description' => 'Send mail, cron and debug options.',
    'type' => MENU_NORMAL_ITEM,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_settings_mail',
    ),
    'access arguments' => array(
      'administer simplenews settings',
    ),
    'file' => 'simplenews.admin.inc',
    'weight' => -7,
  );
  $items['newsletter/confirm'] = array(
    'title' => 'Confirm newsletter subscriptions',
    'type' => MENU_CALLBACK,
    'page callback' => 'simplenews_confirm_subscription',
    'access arguments' => array(
      'subscribe to newsletters',
    ),
    'file' => 'simplenews.subscription.inc',
  );
  $items['newsletter/subscriptions'] = array(
    'title' => 'Manage newsletter subscriptions',
    'type' => MENU_CALLBACK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_subscription_manager_form',
    ),
    'access arguments' => array(
      'subscribe to newsletters',
    ),
    'file' => 'simplenews.subscription.inc',
  );
  return $items;
}

/**
 * Menu item access callback.
 *
 * Access for both newsletter and subscriber admins.
 */
function simplenews_newsletter_access() {
  return user_access('administer newsletters') || user_access('administer simplenews subscriptions');
}

/**
 * Implementation of hook_node_type().
 *
 * Update 'simplenews_content_types' on node update and delete.
 * Related vocabulary settings are updated by taxonomy module.
 */
function simplenews_node_type($op, $info) {
  switch ($op) {
    case 'delete':
      $simplenews_types = variable_get('simplenews_content_types', array());
      if (isset($simplenews_types[$info->type])) {
        unset($simplenews_types[$info->type]);
        variable_set('simplenews_content_types', $simplenews_types);
      }
      break;
    case 'update':
      $simplenews_types = variable_get('simplenews_content_types', array());
      if (isset($info->old_type) && isset($simplenews_types[$info->old_type]) && $info->old_type != $info->type) {
        unset($simplenews_types[$info->old_type]);
        $simplenews_types[$info->type] = $info->type;
        variable_set('simplenews_content_types', $simplenews_types);
      }
      break;
  }
}

/**
 * Implementation of hook_nodeapi().
 */
function simplenews_nodeapi(&$node, $op, $teaser, $page) {
  global $user;

  // Operate only on node types set in 'simplenews_content_types' variable.
  if (!in_array($node->type, variable_get('simplenews_content_types', array(
    'simplenews',
  )))) {
    return;
  }
  switch ($op) {
    case 'alter':

      // Don't replace the tokens when node alter is called by simplenews_mail.
      if (!isset($node->simplenews_mail)) {
        global $language;
        $context['node'] = $node;
        $variables = simplenews_mail_tokens($user, $context, $language);
        if (isset($node->body)) {
          $node->body = strtr($node->body, $variables);
        }
        if (isset($node->teaser)) {
          $node->teaser = strtr($node->teaser, $variables);
        }
      }
      break;
    case 'validate':
      $vid = variable_get('simplenews_vid', '');
      if (!isset($node->taxonomy[$vid]) || empty($node->taxonomy[$vid]) || simplenews_validate_taxonomy($node->taxonomy) == FALSE) {
        form_set_error('taxonomy', t('No newsletter term is selected, the newsletter taxonomy term is probably not configured correctly.<br /> Check and <strong>save</strong> the <a href="@settings">Simplenews general settings</a>.', array(
          '%name' => $vocabulary->name,
          '@settings' => url('admin/settings/simplenews/general'),
        )));
      }
      elseif (isset($node->simplenews['send']) && $node->simplenews['send'] == SIMPLENEWS_COMMAND_SEND_TEST) {
        if (!empty($node->simplenews['test_address'])) {
          $mails = explode(',', $node->simplenews['test_address']);
          foreach ($mails as $mail) {
            $mail = trim($mail);
            if ($mail == '') {
              form_set_error('simplenews][test_address', t('Test email address is empty.'));
            }
            elseif (!valid_email_address($mail)) {
              form_set_error('simplenews][test_address', t('Invalid email address %mail.', array(
                '%mail' => $mail,
              )));
            }
          }
        }
        else {
          form_set_error('simplenews][test_address', t('Missing test email address.'));
        }
      }
      break;
    case 'presave':
      $term = simplenews_validate_taxonomy($node->taxonomy);
      $tid = is_array($term) ? array_values($term) : FALSE;
      $node->simplenews['tid'] = $tid ? $tid[0] : 0;
      break;
    case 'insert':
    case 'update':

      // Flatten the simplenews 'advanced' settings array.
      // This makes sure the $node array after insert/update is equal to the
      // $node array after load.
      $node->simplenews = _simplenews_flatten_array($node->simplenews);
      $send_with_permission = $node->simplenews['send'] == SIMPLENEWS_COMMAND_SEND_NOW && user_access('send newsletter');
      if ($op == 'insert') {

        // Insert node
        $s_status = $send_with_permission ? SIMPLENEWS_STATUS_SEND_PENDING : SIMPLENEWS_STATUS_SEND_NOT;
        db_query("INSERT INTO {simplenews_newsletters} (nid, vid, tid, s_status, s_format, priority, receipt)\n                  VALUES (%d, %d, %d, %d, '%s', %d, %d)", $node->nid, $node->vid, $node->simplenews['tid'], $s_status, $node->simplenews['s_format'], $node->simplenews['priority'], $node->simplenews['receipt']);
      }
      else {

        // Update node
        if ($send_with_permission) {
          db_query("UPDATE {simplenews_newsletters} SET vid = %d, tid = %d, s_status = %d, s_format = '%s', priority = %d, receipt = %d\n          WHERE nid = %d", $node->vid, $node->simplenews['tid'], SIMPLENEWS_STATUS_SEND_PENDING, $node->simplenews['s_format'], $node->simplenews['priority'], $node->simplenews['receipt'], $node->nid);
        }
        else {
          db_query("UPDATE {simplenews_newsletters} SET tid = %d, s_format = '%s', priority = %d, receipt = %d\n          WHERE nid = %d", $node->simplenews['tid'], $node->simplenews['s_format'], $node->simplenews['priority'], $node->simplenews['receipt'], $node->nid);
        }
      }

      // When this node is selected for translation all translation of this node
      // will be sent too.
      // All translated nodes will receive the same send states (priority, confirmation, format).
      if (module_exists('translation') && translation_supported_type($node->type) && $send_with_permission) {
        if ($translations = translation_node_get_translations($node->tnid)) {
          foreach ($translations as $translation) {
            db_query("UPDATE {simplenews_newsletters} SET s_status = %d, s_format = '%s', priority = %d, receipt = %d\n              WHERE nid = %d", SIMPLENEWS_STATUS_SEND_PENDING, $node->simplenews['s_format'], $node->simplenews['priority'], $node->simplenews['receipt'], $translation->nid);
          }
        }
      }

      // Send newsletter or test newsletter
      if ($send_with_permission) {

        // Send newsletter to all subscribers
        simplenews_send_node($node);
      }
      elseif ($node->simplenews['send'] == SIMPLENEWS_COMMAND_SEND_TEST) {

        // Send test newsletter to test address(es)
        simplenews_send_test($node);
      }
      break;
    case 'delete':
      $result = db_query('DELETE FROM {simplenews_newsletters} WHERE nid = %d', $node->nid);
      if ($result) {
        drupal_set_message(t('Newsletter %title was deleted.', array(
          '%title' => $node->title,
        )));
      }
      break;
    case 'load':
      $node->simplenews = db_fetch_array(db_query('SELECT * FROM {simplenews_newsletters} WHERE nid = %d', $node->nid));
      break;
  }
}

/**
 * Validate if selected terms are Newsletter taxonomy terms.
 *
 * @param array $taxonomy Taxonomy form array of newsletter node.
 *
 * @return
 *   Array of selected Newsletter terms. Example: array(4, 12)
 *   FALSE: no Newsletter term is selected
 *
 * NOTE: This function can not handle free tagging tags.
 *       In case of free tagging see taxonomy_node_save() for example code.
 *       Note that free tagging can create new terms at node add/edit. This
 *       contradicts with the current set-up of simpelnews.
 */
function simplenews_validate_taxonomy($taxonomy) {

  // Get newsletter tids.
  $vid = variable_get('simplenews_vid', '');
  $result = db_query('SELECT tid FROM {term_data} WHERE vid = %d', $vid);
  while ($tid = db_fetch_object($result)) {
    $newsletter_tids[] = $tid->tid;
  }

  // Extract selected tid's from the taxonomy form.
  if (isset($newsletter_tids) && !empty($taxonomy)) {
    $selected_terms = array();
    if (is_array($taxonomy)) {
      foreach ($taxonomy as $term) {
        if (is_array($term)) {
          foreach ($term as $tid) {
            if ($tid) {
              $selected_terms[] = $tid;
            }
          }
        }
        else {
          if (is_object($term)) {
            $selected_terms[] = $term->tid;
          }
          else {
            if ($term) {
              $selected_terms[] = $term;
            }
          }
        }
      }
    }

    // Compare selected tid's and newsletter tid's.
    $valid_terms = array_intersect($newsletter_tids, $selected_terms);
    return empty($valid_terms) ? FALSE : $valid_terms;
  }
  return FALSE;
}

/**
 * Implementation of hook_form_alter().
 */
function simplenews_form_alter(&$form, $form_state, $form_id) {
  $vid = variable_get('simplenews_vid', '');

  // Newsletter vocabulary form
  if ($form_id == 'taxonomy_form_vocabulary' && isset($form['vid']) && $form['vid']['#value'] == $vid) {

    // Hide critical options from newsletter vocabulary.
    $form['help_simplenews_vocab'] = array(
      '#value' => t('This is the designated simplenews vocabulary.'),
      '#weight' => -1,
    );

    // We display the current content type settings in a disabled form element
    // to the user. The real value passed in the form separately because
    // disabled elements do not get saved at submit.
    $form['content_types']['display_only'] = $form['content_types']['nodes'];
    $form['content_types']['display_only']['#disabled'] = TRUE;
    $form['content_types']['display_only']['#description'] = t('These content type(s) are used as newsletter. They can be set in !simplenews_settings.', array(
      '!simplenews_settings' => l('Simplenews settings', 'admin/settings/simplenews'),
    ));
    $form['content_types']['nodes'] = array(
      '#type' => 'value',
      '#value' => $form['content_types']['nodes']['#default_value'],
    );

    // Free tagging can not be allowed see: simplenews_validate_taxonomy().
    $form['settings']['tags'] = array(
      '#type' => 'value',
      '#value' => FALSE,
    );

    // Multiple select does not work with simplenews.
    $form['settings']['multiple'] = array(
      '#type' => 'value',
      '#value' => FALSE,
    );
    $form['settings']['required'] = array(
      '#type' => 'value',
      '#value' => TRUE,
    );
  }
  elseif (isset($form['type']) && isset($form['#node']) && strpos($form_id, '_node_form')) {
    if (in_array($form['type']['#value'], variable_get('simplenews_content_types', array(
      'simplenews',
    )))) {

      // Available variables are based on user_mail_tokens().
      // But uses only those which can be used with uid = 0 since simplenews also sends to anonymous users.
      if (isset($form['body_field'])) {
        $form['body_field']['body']['#description'] = t("This will be the body of your newsletter. Available variables are: !site (the name of your website), !uri (a link to your homepage), !uri_brief (homepage link without the http://), !date (today's date), !login_uri (link to login page), !confirm_subscribe_url (subscription confirmation link), !confirm_unsubscribe_url (unsubscription link), !newsletter_url (link to this newsletter issue), !newsletter_name (name of this newsletter series).");
      }
      if (isset($form['#node']->simplenews)) {
        $simplenews_values = $form['#node']->simplenews;
      }
      $vocabulary = taxonomy_vocabulary_load(variable_get('simplenews_vid', ''));
      if (!empty($vocabulary) && !isset($vocabulary->nodes[$form['type']['#value']])) {
        drupal_set_message(t('Invalid vocabulary setting detected. Check and <strong>save</strong> the <a href="@settings">Simplenews general settings</a>.', array(
          '%name' => $vocabulary->name,
          '@settings' => url('admin/settings/simplenews'),
        )), 'error');
      }
      $form['simplenews'] = array(
        '#type' => 'fieldset',
        '#title' => t('Send newsletter'),
        '#collapsible' => TRUE,
        '#collapsed' => FALSE,
        '#tree' => TRUE,
      );

      // Translations of newsletters don't have send and format options. Only the
      // translation source (and non translated) newsletters will get these options.
      if (module_exists('translation') && translation_supported_type($form['#node']->type) && (isset($form['#node']->translation_source) || $form['#node']->tnid && $form['#node']->tnid != $form['#node']->nid)) {
        $form['simplenews']['#description'] = t('This newsletter issue is part of a translation set. Sending this set is controlled from the <a href="@link">translation source newsletter</a>.', array(
          '@link' => url('node/' . $form['tnid']['#value']),
        ));

        // Send option of translated newsletters are not used, but a default is
        // required to prevent errors when data is stored in the database.
        $form['simplenews']['send'] = array(
          '#type' => 'hidden',
          '#value' => SIMPLENEWS_COMMAND_SEND_NONE,
        );
        $form['simplenews']['advanced']['s_format'] = array(
          '#type' => 'hidden',
          '#value' => variable_get('simplenews_format', 'plain'),
        );
        $form['simplenews']['advanced']['priority'] = array(
          '#type' => 'hidden',
          '#value' => variable_get('simplenews_priority', SIMPLENEWS_PRIORITY_NONE),
        );
        $form['simplenews']['advanced']['receipt'] = array(
          '#type' => 'hidden',
          '#value' => variable_get('simplenews_receipt', 0),
        );
      }
      else {

        // Show newsletter sending options if newsletter has not been send yet.
        // If send a nodification is shown.
        if (!isset($simplenews_values['s_status']) || isset($simplenews_values['s_status']) && $simplenews_values['s_status'] == SIMPLENEWS_STATUS_SEND_NOT) {

          // Add dynamic text for send button.
          drupal_add_js(drupal_get_path('module', 'simplenews') . '/simplenews.js', 'module');
          if (user_access('send newsletter')) {
            $options[SIMPLENEWS_COMMAND_SEND_NONE] = t("Don't send now");
            $options[SIMPLENEWS_COMMAND_SEND_TEST] = t('Send one test newsletter to the test address');
            $options[SIMPLENEWS_COMMAND_SEND_NOW] = t('Send newsletter');
            $form['simplenews']['send'] = array(
              '#type' => 'radios',
              '#title' => t('Send action'),
              '#default_value' => isset($simplenews_values['send']) ? $simplenews_values['send'] : variable_get('simplenews_send', SIMPLENEWS_COMMAND_SEND_NONE),
              '#options' => $options,
              '#attributes' => array(
                'class' => 'simplenews-command-send',
              ),
            );
          }
          else {
            $options[SIMPLENEWS_COMMAND_SEND_NONE] = t("Don't send now");
            $options[SIMPLENEWS_COMMAND_SEND_TEST] = t('Send one test newsletter to the test address');
            $form['simplenews']['send'] = array(
              '#type' => 'radios',
              '#title' => t('Sending'),
              '#default_value' => isset($simplenews_values['send']) ? $simplenews_values['send'] : SIMPLENEWS_COMMAND_SEND_NONE,
              '#options' => $options,
              '#description' => t('You have no privileges to send this newsletter'),
              '#attributes' => array(
                'class' => 'simplenews-command-send',
              ),
            );
          }
          $address_default = variable_get('site_mail', ini_get('sendmail_from'));
          if (variable_get('simplenews_test_address_override', 0)) {
            $form['simplenews']['test_address'] = array(
              '#type' => 'textfield',
              '#title' => t('Test email addresses'),
              '#description' => t('Supply a comma-separated list of email addresses to be used as test addresses.'),
              '#default_value' => isset($simplenews_values['test_address']) ? $simplenews_values['test_address'] : variable_get('simplenews_test_address', $address_default),
              '#size' => 60,
              '#maxlength' => 128,
            );
          }
          else {
            $form['simplenews']['test_address'] = array(
              '#type' => 'hidden',
              '#value' => variable_get('simplenews_test_address', $address_default),
            );
          }
          $form['simplenews']['advanced'] = array(
            '#type' => 'fieldset',
            '#title' => t('Email options'),
            '#collapsible' => TRUE,
            '#collapsed' => TRUE,
          );

          // Hide format selection if there is nothing to choose.
          // The default format is plain text.
          $format_options = _simplenews_format_options();
          if (count($format_options) > 1) {
            $form['simplenews']['advanced']['s_format'] = array(
              '#type' => 'select',
              '#title' => t('Email format'),
              '#default_value' => isset($simplenews_values['s_format']) ? $simplenews_values['s_format'] : variable_get('simplenews_format', 'plain'),
              '#options' => _simplenews_format_options(),
            );
          }
          else {
            $form['simplenews']['advanced']['s_format'] = array(
              '#type' => 'hidden',
              '#value' => key($format_options),
            );
          }
          $form['simplenews']['advanced']['priority'] = array(
            '#type' => 'select',
            '#title' => t('Email priority'),
            '#default_value' => isset($simplenews_values['priority']) ? $simplenews_values['priority'] : variable_get('simplenews_priority', SIMPLENEWS_PRIORITY_NONE),
            '#options' => 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'),
            ),
          );
          $form['simplenews']['advanced']['receipt'] = array(
            '#type' => 'checkbox',
            '#title' => t('Request receipt'),
            '#return_value' => 1,
            '#default_value' => isset($simplenews_values['receipt']) ? $simplenews_values['receipt'] : variable_get('simplenews_receipt', 0),
          );
        }
        else {
          $form['simplenews']['none'] = array(
            '#type' => 'checkbox',
            '#title' => t('This newsletter has been sent'),
            '#return_value' => 0,
            '#attributes' => array(
              'checked' => 'checked',
              'disabled' => 'disabled',
            ),
          );
          $form['simplenews']['advanced']['s_format'] = array(
            '#type' => 'hidden',
            '#value' => $simplenews_values['s_format'],
          );
          $form['simplenews']['advanced']['priority'] = array(
            '#type' => 'hidden',
            '#value' => $simplenews_values['priority'],
          );
          $form['simplenews']['advanced']['receipt'] = array(
            '#type' => 'hidden',
            '#value' => $simplenews_values['receipt'],
          );
        }
      }
      $form['simplenews']['s_status'] = array(
        '#type' => 'hidden',
        '#value' => isset($simplenews_values['s_status']) ? $simplenews_values['s_status'] : SIMPLENEWS_STATUS_SEND_NOT,
      );
    }
  }
}

/**
 * Implementation of hook_cron().
 */
function simplenews_cron() {
  simplenews_mail_spool();
  simplenews_clear_spool();

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

/**
 * Implementation of hook_taxonomy().
 *
 * Deletes subscriptions to term when term is deleted, and cleans the blocks
 * table.
 */
function simplenews_taxonomy($op, $type, $term = NULL) {
  if ($op == 'delete' && $term['vid'] == variable_get('simplenews_vid', '')) {
    switch ($type) {
      case 'term':

        //TODO subscriber is not deleted when last subscription is removed.
        db_query('DELETE FROM {simplenews_snid_tid} WHERE tid = %d', $term['tid']);
        db_query("DELETE FROM {blocks} WHERE module = '%s' AND delta = '%s'", 'simplenews', $term['tid']);
        drupal_set_message(t('All subscriptions to newsletter %newsletter have been deleted.', array(
          '%newsletter' => $term['name'],
        )));
        break;
    }
  }
}

/**
 * Implementation of hook_user().
 *
 * Checks whether an email address is subscribed to the newsletter when a new
 * user signs up. If so, changes uid from 0 to the new uid in
 * simplenews_subscriptions so that the user's subscription status is known when
 * he logs in.
 */
function simplenews_user($op, &$edit, &$account, $category = NULL) {
  switch ($op) {
    case 'insert':
      if ($edit['mail']) {
        $query = "SELECT snid FROM {simplenews_subscriptions} WHERE mail = '%s'";
        if ($result = db_fetch_object(db_query($query, $edit['mail']))) {
          db_query("UPDATE {simplenews_subscriptions} SET uid = %d, language = '%s' WHERE snid = %d", $edit['uid'], $edit['language'], $result->snid);
        }
      }
      break;
    case 'update':
      if ($category == 'account' && $edit['mail']) {
        $query = "SELECT snid FROM {simplenews_subscriptions} WHERE uid = %d";
        if ($result = db_fetch_object(db_query($query, $account->uid))) {
          db_query("DELETE FROM {simplenews_subscriptions} WHERE mail = '%s' AND uid = %d", $edit['mail'], 0);
          db_query("UPDATE {simplenews_subscriptions} SET mail = '%s', language = '%s' WHERE snid = %d", $edit['mail'], $edit['language'], $result->snid);
        }
        else {
          $query = "SELECT snid FROM {simplenews_subscriptions} WHERE mail = '%s'";
          if ($result = db_fetch_object(db_query($query, $edit['mail']))) {
            db_query("UPDATE {simplenews_subscriptions} SET uid = %d, language = '%s' WHERE snid = %d", $account->uid, $account->language, $result->snid);
          }
        }
      }

      // Activate/deactivate subscription when account is blocked/unblocked
      if ($category == 'account' && isset($edit['status'])) {
        if (variable_get('simplenews_sync_account', TRUE)) {
          db_query("UPDATE {simplenews_subscriptions} SET activated = %d WHERE uid = %d", $edit['status'], $account->uid);
        }
      }
      if ($category == 'newsletter' && (user_access('subscribe to newsletters', $account) || user_access('administer users'))) {
        foreach ($edit['newsletters'] as $tid => $checked) {
          if ($checked) {
            simplenews_subscribe_user($account->mail, $tid, FALSE);
          }
          else {
            simplenews_unsubscribe_user($account->mail, $tid, FALSE);
          }
        }
      }
      break;
    case 'delete':
      if (variable_get('simplenews_sync_account', TRUE)) {

        // Delete subscription and all newsletter subscriptions when account is removed.
        // We don't use simplenews_get_subscription() here because the user is already
        // deleted from the {user} table.
        $snid = db_result(db_query("SELECT s.snid FROM {simplenews_subscriptions} s WHERE s.mail = '%s'", $account->mail));
        db_query('DELETE FROM {simplenews_snid_tid} WHERE snid = %d', $snid);
        db_query('DELETE FROM {simplenews_subscriptions} WHERE snid = %d', $snid);
      }
      else {

        // Only remove uid from subscription data when account is removed
        db_query("UPDATE {simplenews_subscriptions} SET uid = 0 WHERE uid = %d", $account->uid);
      }
      break;
    case 'form':
      if ($category == 'newsletter') {
        $subscription = simplenews_get_subscription($account);
        $form = _simplenews_subscription_manager_form($subscription);
        $form['subscriptions']['#title'] = t('Current newsletter subscriptions');
        unset($form['update'], $form['subscriptions']['mail']);
        return $form;
      }
      break;
    case 'categories':

      // Newsletter tab with custom permission check.
      $output[] = array(
        'name' => 'newsletter',
        'title' => t('My newsletters'),
        'weight' => 10,
        'access callback' => 'simplenews_subscription_edit_access',
      );
      return $output;
      break;
    case 'view':
      global $user;
      if ($user->uid == $account->uid || user_access('administer users')) {
        $account->content['simplenews'] = array(
          '#type' => 'user_profile_category',
          '#title' => t('Newsletters'),
        );
        $tree = taxonomy_get_tree(variable_get('simplenews_vid', ''));
        foreach ($tree as $newsletter) {
          if (db_result(db_query('SELECT COUNT(s.uid) FROM {simplenews_subscriptions} s INNER JOIN {simplenews_snid_tid} t ON s.snid = t.snid WHERE s.uid = %d AND t.tid = %d', $account->uid, $newsletter->tid))) {
            $subscriptions[] = l($newsletter->name, 'taxonomy/term/' . $newsletter->tid);
          }
        }
        if (isset($subscriptions)) {
          $subscriptions = implode(', ', $subscriptions);
        }
        else {
          $subscriptions = 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', $account) || $subscriptions != t('None')) {
          $account->content['simplenews']['subscriptions'] = array(
            '#type' => 'user_profile_item',
            '#title' => t('Current subscriptions'),
            '#value' => $subscriptions,
          );
        }
        if (user_access('subscribe to newsletters', $account)) {
          $account->content['simplenews']['my_newsletters'] = array(
            '#type' => 'user_profile_item',
            '#value' => t('Manage <a href="!url">my subscriptions</a>', array(
              '!url' => url('user/' . $account->uid . '/edit/newsletter'),
            )),
            '#weight' => -1,
          );
        }
      }
      break;
  }
}

/**
 * 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;
}

/**
 * Implementation of hook_block().
 */
function simplenews_block($op = 'list', $delta = 0, $edit = array()) {
  switch ($op) {
    case 'list':
      $blocks = array();
      foreach (taxonomy_get_tree(variable_get('simplenews_vid', '')) 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->tid] = array(
          'info' => t('Newsletter: @title', array(
            '@title' => $newsletter->name,
          )),
          'cache' => variable_get('simplenews_block_f_' . $newsletter->tid, 1) ? BLOCK_NO_CACHE : BLOCK_CACHE_PER_ROLE,
        );
      }
      return $blocks;
    case 'configure':
      $form['simplenews_block_' . $delta]['simplenews_block_m_' . $delta] = array(
        '#type' => 'textfield',
        '#title' => t('Block message'),
        '#size' => 60,
        '#maxlength' => 128,
        '#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),
      );
      $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),
      );
      $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),
      );
      $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),
      );
      return $form;
    case 'save':
      variable_set('simplenews_block_m_' . $delta, $edit['simplenews_block_m_' . $delta]);
      variable_set('simplenews_block_f_' . $delta, $edit['simplenews_block_f_' . $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]);
      variable_set('simplenews_block_r_' . $delta, $edit['simplenews_block_r_' . $delta]);
      break;
    case 'view':
      global $language;
      if ($tree = taxonomy_get_tree(variable_get('simplenews_vid', ''))) {

        // Only display a block if $delta is a valid newsletter term id.
        foreach ($tree as $taxonomy) {
          $tids[] = $taxonomy->tid;
        }
        if (in_array($delta, $tids)) {

          // $delta is validated, the block can be displayed.
          $newsletter = (array) taxonomy_get_term($delta);

          // Translate newsletter name if required.
          if (module_exists('i18ntaxonomy') && i18ntaxonomy_vocabulary(variable_get('simplenews_vid', '')) == I18N_TAXONOMY_LOCALIZE) {
            $newsletter['name'] = tt('taxonomy:term:' . $newsletter['tid'] . ':name', $newsletter['name'], $language->language);
          }
          $block = array(
            'subject' => check_plain($newsletter['name']),
            'content' => theme(array(
              'simplenews_block__' . $newsletter['tid'],
              'simplenews_block',
            ), $newsletter['tid']),
          );
          return $block;
        }
      }
      break;
  }
}

/**
 * Implementation of 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() {
  if ($taxonomies = taxonomy_get_tree(variable_get('simplenews_vid', ''))) {
    foreach ($taxonomies as $newsletter) {
      $forms['simplenews_block_form_' . $newsletter->tid] = array(
        'callback' => 'simplenews_block_form',
        'callback arguments' => array(
          $newsletter->tid,
        ),
      );
    }
  }
  return $forms;
}

/**
 * Load a user or creates a dummy anonymous user.
 *
 * @return account
 *   object (
 *     mail,   email address
 *     uid,    uid or 0 for anonymous
 *   )
 */
function _simplenews_user_load($mail) {
  $account = user_load(array(
    'mail' => $mail,
  ));
  if ($account === FALSE) {

    // Construct anonymous user since we don't have a user that matches that e-amil.
    $account = new stdClass();
    $account->uid = 0;
    $account->mail = drupal_strtolower($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 $tid
 *   The term ID of the newsletter.
 * @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.
 */
function simplenews_subscribe_user($mail, $tid, $confirm = TRUE, $preferred_language = NULL) {
  global $language;

  //Prevent mismatches from accidental capitals in mail address
  $mail = strtolower($mail);

  // Get current subscriptions if any.
  $account = (object) array(
    'mail' => $mail,
  );
  $subscription = simplenews_get_subscription($account);

  // If user is not subscribed to ANY newsletter, create a subscription account
  if ($subscription->snid == 0) {

    // 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_user_load($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;
      }
    }
    db_query("INSERT INTO {simplenews_subscriptions} (mail, uid, language, activated) VALUES ('%s', %d, '%s', %d)", $mail, $account->uid, $preferred_language, 1);
    $subscription = simplenews_get_subscription($account);
  }
  if ($confirm) {

    // Send confirmation email to user to complete subscription or to tell
    // them that he or she is already subscribed.
    // Confirmation mail is in the user preferred language which is by default the language_default().
    $params['from'] = _simplenews_set_from();
    $params['context']['newsletter'] = taxonomy_get_term($tid);
    $params['context']['account'] = $subscription;
    drupal_mail('simplenews', 'subscribe', $mail, $subscription->language, $params, $params['from']['address']);
  }
  elseif (!isset($subscription->tids[$tid])) {

    // OR add user to newsletter relationship if not already subscribed.
    db_query("INSERT INTO {simplenews_snid_tid} (snid, tid) VALUES (%d, %d)", $subscription->snid, $tid);

    // Execute simplenews subscribe trigger.
    simplenews_call_actions('subscribe', $subscription);
  }
  return TRUE;
}

/**
 * Unsubscribe a user from a newsletter 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 unsubscription
 * The subscription account is deleted when the user is unsubscribed to the last newsletter
 *
 * @param string $mail The email address to unsubscribe from the newsletter.
 * @param integer $tid The term ID of the newsletter.
 * @param boolean $confirm TRUE = send confirmation mail; FALSE = unsubscribe immediate from the newsletter
 */
function simplenews_unsubscribe_user($mail, $tid, $confirm = TRUE) {

  //Prevent mismatches from accidental capitals in mail address
  $mail = strtolower($mail);
  $account = (object) array(
    'mail' => $mail,
  );
  $subscription = simplenews_get_subscription($account);

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

    // Send confirmation email to user to complete unsubscription
    // or to tell them that he or she is not subscribed
    // Confirmation mail is in the user preferred language.
    $params['from'] = _simplenews_set_from();
    $params['context']['newsletter'] = $newsletter;
    $params['context']['account'] = $subscription;
    drupal_mail('simplenews', 'unsubscribe', $mail, $subscription->language, $params, $params['from']['address']);
  }
  elseif (isset($subscription->tids[$tid])) {

    // OR remove the user from the newsletter.
    db_query('DELETE FROM {simplenews_snid_tid} WHERE snid = %d AND tid = %d', $subscription->snid, $tid);

    // Clean up subscription account if user is not subscribed to any newsletter anymore
    if (!db_result(db_query("SELECT COUNT(*) FROM {simplenews_snid_tid} t WHERE t.snid = %d", $subscription->snid))) {
      db_query('DELETE FROM {simplenews_subscriptions} WHERE snid = %d', $subscription->snid);
    }

    // Execute simplenews unsubscribe trigger
    simplenews_call_actions('unsubscribe', $subscription);
  }
  return TRUE;
}

/**
 * Check if the email address is subscribed to the given newsletter.
 *
 * @param string $mail email address
 * @param integer $tid newsletter term id
 *
 * @return boolean TRUE = email address is subscribed to given newsletter term id
 */
function simplenews_user_is_subscribed($mail, $tid, $reset = FALSE) {
  static $subscribed = array();
  if ($reset) {
    unset($subscribed);
  }
  if (!isset($subscribed[$mail][$tid])) {
    $subscribed[$mail][$tid] = db_result(db_query("SELECT COUNT(*) FROM {simplenews_subscriptions} s INNER JOIN {simplenews_snid_tid} t ON s.snid = t.snid WHERE s.mail = '%s' AND t.tid = %d", $mail, $tid)) ? TRUE : FALSE;
  }
  return $subscribed[$mail][$tid];
}

/**
 * Get the subscription object for the given account.
 *
 * Account is defined by (in order of preference) snid, email address or uid.
 * If the account is not subscribed a default subscription object is returned
 * containing all available account info.
 *
 * @param object $account account details. Containing one or none of these items:
 *  object(
 *    snid :  subscription id
 *    mail :  email address
 *    uid  :  user id
 *  )
 *
 * @return subscription object
 *   object(
 *     snid :  subscription id. 0 if account is not subscribed
 *     tids :  array of newsletter tid's
 *     uid  :  user id. 0 if account is anonymous user
 *     mail :  user email address. empty if email is unknown
 *     name :  always empty. Added for compatebility with user account object
 *     language : language object. User preferred or default language
 *   )
 */
function simplenews_get_subscription($account) {

  // Load subscription data based on available account informatioin
  // NOTE that the order of checking for snid, mail and uid is critical. mail must be checked *before* uid. See simplenews_subscribe_user()
  if (isset($account->snid)) {
    $subscription = db_fetch_object(db_query("SELECT s.* FROM {simplenews_subscriptions} s LEFT JOIN {users} u ON u.uid = s.uid WHERE s.snid = %d", $account->snid));
  }
  elseif (isset($account->mail)) {
    $subscription = db_fetch_object(db_query("SELECT s.* FROM {simplenews_subscriptions} s LEFT JOIN {users} u ON u.uid = s.uid WHERE s.mail = '%s'", $account->mail));
  }
  elseif (isset($account->uid) && $account->uid > 0) {
    $subscription = db_fetch_object(db_query("SELECT s.* FROM {simplenews_subscriptions} s LEFT JOIN {users} u ON u.uid = s.uid WHERE s.uid = %d", $account->uid));
  }
  if (!empty($subscription)) {
    $result = db_query("SELECT tid FROM {simplenews_snid_tid} t WHERE t.snid = %d", $subscription->snid);
    $subscription->tids = array();
    while ($newsletter = db_fetch_object($result)) {
      $subscription->tids[$newsletter->tid] = $newsletter->tid;
    }
    $subscription->name = '';
    $subscription->language = user_preferred_language($subscription);
  }
  else {

    // Account is unknown in subscription table. Create default subscription object
    $subscription = new stdClass();
    $subscription->name = '';
    $subscription->uid = isset($account->uid) ? $account->uid : 0;
    $subscription->mail = isset($account->mail) ? $account->mail : '';
    $subscription->language = language_default();
    $subscription->snid = 0;
    $subscription->tids = array();
  }
  return $subscription;
}

/**
 * Delete every subscription for the given subscription ID.
 *
 * @param integer $snid subscription id
 */
function simplenews_delete_subscription($snid) {
  $account = db_fetch_object(db_query('SELECT mail FROM {simplenews_subscriptions} WHERE snid = %d', $snid));
  db_query('DELETE FROM {simplenews_subscriptions} WHERE snid = %d', $snid);
  db_query('DELETE FROM {simplenews_snid_tid} WHERE snid = %d', $snid);
  watchdog('simplenews', 'User %email deleted from the mailing list.', array(
    '%email' => $account->mail,
  ), WATCHDOG_NOTICE);
}

/**
 * Build subscription manager form.
 *
 * @param object $subscription subscription object
 */
function _simplenews_subscription_manager_form($subscription) {
  $form = array();
  $options = array();
  $default_value = array();
  global $language;

  // Get taxonomy terms for subscription form checkboxes.
  // With taxonomy translation and 'Per language terms' only the terms of the
  // current language are listed. With taxonomy translation and 'Localize terms'
  // all taxonomy terms are listed and translated.
  if (module_exists('i18ntaxonomy') && i18ntaxonomy_vocabulary(variable_get('simplenews_vid', '')) == I18N_TAXONOMY_TRANSLATE) {

    // Per language terms.
    $tterms = i18ntaxonomy_vocabulary_get_terms(variable_get('simplenews_vid', ''), $language->language);
    foreach ($tterms as $tid => $name) {
      $options[$tid] = check_plain($name);
      $default_value[$tid] = FALSE;
    }
  }
  else {
    foreach (taxonomy_get_tree(variable_get('simplenews_vid', '')) as $newsletter) {
      if (module_exists('i18ntaxonomy') && i18ntaxonomy_vocabulary(variable_get('simplenews_vid', '')) == I18N_TAXONOMY_LOCALIZE) {

        // Localize terms.
        $options[$newsletter->tid] = check_plain(tt('taxonomy:term:' . $newsletter->tid . ':name', $newsletter->name, $language->language));
      }
      else {

        // Untranslated.
        $options[$newsletter->tid] = check_plain($newsletter->name);
      }
      $default_value[$newsletter->tid] = FALSE;
    }
  }
  $form['subscriptions'] = array(
    '#type' => 'fieldset',
    '#description' => t('Select the newsletter(s) to which you want to subscribe or unsubscribe.'),
  );
  $form['subscriptions']['newsletters'] = array(
    '#type' => 'checkboxes',
    '#options' => $options,
    '#default_value' => array_merge($default_value, (array) $subscription->tids),
  );

  // If current user is logged in, just display email.
  // Anonymous users see an email box and will receive confirmations
  if ($subscription->mail) {
    $form['subscriptions']['#title'] = t('Subscriptions for %mail', array(
      '%mail' => $subscription->mail,
    ));
    $form['subscriptions']['mail'] = array(
      '#type' => 'value',
      '#value' => $subscription->mail,
    );
    $form['update'] = array(
      '#type' => 'submit',
      '#value' => t('Update'),
      '#weight' => 20,
    );
  }
  else {
    $form['subscriptions']['#title'] = t('Manage your newsletter subscriptions');
    $form['subscriptions']['mail'] = array(
      '#type' => 'textfield',
      '#title' => t('email'),
      '#size' => 20,
      '#maxlength' => 128,
      '#weight' => 10,
      '#required' => TRUE,
    );
    $form['subscribe'] = array(
      '#type' => 'submit',
      '#value' => t('Subscribe'),
      '#weight' => 20,
    );
    $form['unsubscribe'] = array(
      '#type' => 'submit',
      '#value' => t('Unsubscribe'),
      '#weight' => 30,
    );
  }
  return $form;
}

/**
 * Create a list of recent newsletters.
 *
 * @param integer $tid term id of selected newsletter
 * @param integer $count number of newsletters in the list
 */
function simplenews_recent_newsletters($tid, $count = 5) {
  $result = db_query_range(db_rewrite_sql('SELECT n.nid, n.title, sn.s_status, n.created FROM {node} n INNER JOIN {term_node} t ON n.vid = t.vid INNER JOIN {simplenews_newsletters} sn ON n.nid = sn.nid WHERE (t.tid = %d AND n.status = %d) ORDER BY n.created DESC'), $tid, 1, 0, $count);
  $titles = array();
  while ($item = db_fetch_object($result)) {
    $titles[$item->nid]['data'] = l($item->title, 'node/' . $item->nid);
    $titles[$item->nid]['class'] = $item->s_status == SIMPLENEWS_STATUS_SEND_NOT ? 'newsletter-created' : 'newsletter-send';
  }
  return $titles;
}

/**
 * Newsletter (un)subscription form for authenticated and anonymous users.
 *
 * @param $tid term id of selected newsletter.
 *
 * @see simplenews_block_form_validate()
 * @see simplenews_block_form_submit()
 */
function simplenews_block_form(&$form_state, $tid) {
  global $user;
  $form = array();
  if ($user->uid) {
    if (simplenews_user_is_subscribed($user->mail, $tid)) {
      $submit_text = t('Unsubscribe');
      $form['action'] = array(
        '#type' => 'value',
        '#value' => 'unsubscribe',
      );
    }
    else {
      $submit_text = t('Subscribe');
      $form['action'] = array(
        '#type' => 'value',
        '#value' => 'subscribe',
      );
    }
    $form['display_mail'] = array(
      '#type' => 'item',
      '#title' => t('User'),
      '#value' => check_plain($user->name),
    );
    $form['mail'] = array(
      '#type' => 'value',
      '#value' => $user->mail,
    );
  }
  else {
    $form['mail'] = array(
      '#type' => 'textfield',
      '#title' => t('Email'),
      '#size' => 20,
      '#maxlength' => 128,
      '#required' => TRUE,
    );
    $form['action'] = array(
      '#type' => 'radios',
      '#default_value' => 'subscribe',
      '#options' => array(
        'subscribe' => t('Subscribe'),
        'unsubscribe' => t('Unsubscribe'),
      ),
    );
  }

  // All block forms use the same validate and submit function.
  // #tid carries the tid for processing of the right newsletter issue term.
  $form['#tid'] = $tid;
  $form['#validate'][] = 'simplenews_block_form_validate';
  $form['#submit'][] = 'simplenews_block_form_submit';
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => isset($submit_text) ? $submit_text : t('Save'),
  );
  return $form;
}
function simplenews_block_form_validate($form, &$form_state) {
  if (!valid_email_address($form_state['values']['mail'])) {
    form_set_error('mail', t("The email address you supplied is not valid."));
  }
}
function simplenews_block_form_submit($form, &$form_state) {
  global $user;
  $tid = $form['#tid'];
  $account = _simplenews_user_load($form_state['values']['mail']);

  // If email belongs to the current registered user, don't send confirmation.
  $confirm = $account->uid && $account->uid == $user->uid ? FALSE : TRUE;
  switch ($form_state['values']['action']) {
    case 'subscribe':
      simplenews_subscribe_user($form_state['values']['mail'], $tid, $confirm);
      if ($confirm) {
        drupal_set_message(t('You will receive a confirmation email shortly containing further instructions on how to complete your subscription.'));
      }
      else {
        drupal_set_message(t('You have been subscribed.'));
      }
      break;
    case 'unsubscribe':
      simplenews_unsubscribe_user($form_state['values']['mail'], $tid, $confirm);
      if ($confirm) {
        drupal_set_message(t('You will receive a confirmation email shortly containing further instructions on how to complete the unsubscription process.'));
      }
      else {
        drupal_set_message(t('You have been unsubscribed.'));
      }
      break;
  }
}

/**
 * Send newsletter node to subcribers.
 *
 * @param integer or object $node Newsletter node to be sent. integer = nid; object = node object
 * @param array $accounts  account objects to send the newsletter to.
 *   account = object (
 *     snid     = subscription id. 0 if no subscription record exists
 *     tids     = array(tid) array of newsletter tid's
 *     uid      = user id. 0 if subscriber is anonymous user.
 *     mail     = user email address.
 *     name     = <empty>. Added for compatebility with user account object
 *     language = language object. User preferred of default language
 *   )
 *   NOTE: either snid, mail or uid is required.
 */
function simplenews_send_node($node, $accounts = array()) {
  $mails = array();
  if (is_numeric($node)) {
    $node = node_load($node);
  }
  if (is_object($node)) {
    $from = _simplenews_set_from($node);
    $params['context']['node'] = $node;
    $params['from'] = $from;
    $node_data['tid'] = $node->simplenews['tid'];
    $node_data['nid'] = $node->nid;
    $node_data['vid'] = $node->vid;
    if (empty($accounts)) {

      // No accounts specified. Get email address of all accounts subscribed to this newsletter.
      $result = db_query('SELECT s.mail FROM {simplenews_subscriptions} s INNER JOIN {simplenews_snid_tid} t ON s.snid = t.snid WHERE s.activated = %d AND t.tid = %d', 1, $node_data['tid']);
      while ($account = db_fetch_object($result)) {
        $mails[] = array(
          'mail' => $account->mail,
        );
      }
    }
    else {

      // Get email address of specified accounts.
      foreach ($accounts as $account) {
        $account = simplenews_get_subscription($account);
        $mails[] = array(
          'mail' => $account->mail,
        );
      }
    }

    // To send the newsletter, the node id and target email addresses
    // are stored in the spool.
    // When cron is not used the newsletter is send immediately to the emails
    // in the spool. When cron is used newsletters are send to addresses in the
    // spool during the next (and following) cron run.
    foreach ($mails as $mail) {
      $data = array_merge($node_data, $mail);
      simplenews_save_spool($data);
    }
    if (variable_get('simplenews_use_cron', TRUE) == FALSE) {
      simplenews_mail_spool($node_data['nid'], $node_data['vid'], 999999);
      drupal_set_message(t('Newsletter sent.'));
      simplenews_clear_spool();
    }
    else {
      drupal_set_message(t('Newsletter pending.'));
    }
  }
}

/**
 * Send test version of newsletter.
 *
 * @param integer or object $node Newsletter node to be sent. Integer = nid; Object = node object
 */
function simplenews_send_test($node) {
  if (is_numeric($node)) {
    $node = node_load($node);
  }
  if (is_object($node)) {

    // Send the test newsletter to the test address(es) specified in the node.
    // Build array of test email addresses
    $mails = explode(',', $node->simplenews['test_address']);

    // Send newsletter to test addresses.
    // Emails are send direct, not using the spool.
    foreach ($mails as $mail) {
      $mail = trim($mail);
      if (!empty($mail)) {
        $result = simplenews_mail_mail($node->nid, $node->vid, $mail, 'test');
      }
    }
    if ($result) {
      drupal_set_message(t('Test newsletter sent to %recipient.', array(
        '%recipient' => $node->simplenews['test_address'],
      )));
    }
  }
}

/**
 * Implementation of hook_mail().
 *
 * Send simplenews mails using drupal mail API
 * @see drupal_mail()
 *
 * @param $key: node | test | subscribe | unsubscribe
 * @param array $message message array
 *          [from]
 *          [headers][From]
 *          [language]            : preferred message language
 * @param array $params parameter array
 *          [context][node]       : node object of message to be sent
 *          [context][snid]       : used for $key = subscribe or unsubscribe
 *          [context][from_name]  : name of mail sender or site name (optional)
 *          [context][account]    : account details of recipient
 *          [from]                : array('address' => 'noreply@example.org', 'formatted' =>  'site name <noreply@example.org>')
 *          [newsletter]          : newsletter object (tid, name)
 *          [tokens]              : tokens for variable replacement. Defaults to: user_mail_tokens()
 */
function simplenews_mail($key, &$message, $params) {
  $context = $params['context'];
  switch ($key) {
    case 'node':
    case 'test':

      // Message header, body and mail headers are buffered to increase
      // perfomance when sending multiple mails. Buffered data only contains
      // general data, no recipient specific content. Tokens are used
      // for recipient data and will later be replaced.
      // When mailing multiple newsletters in one page call or cron run,
      // data is once stored and subsequently retreived from the
      // static $messages variable.
      // $message buffer is node and language specific.
      static $messages = array();

      // By default the the node is send which is supplied in the function call.
      // When translation is used, the availability of translations is checked
      // and when available the translation of the preferred language is selected.
      $nid = $context['node']->nid;
      $langcode = $message['language']->language;
      if (module_exists('translation')) {

        // If the node has translations and a translation is required
        // the equivalent of the node in the required langugage is used
        // or the base node (nid == tnid) is used.
        if ($tnid = $context['node']->tnid) {
          if ($langcode != $context['node']->language) {
            $translations = translation_node_get_translations($tnid);

            // A translation is available in the preferred language.
            if ($translation = $translations[$langcode]) {
              $nid = $translation->nid;
              $langcode = $translation->language;
            }
            else {

              // No translation found which matches the preferred language.
              foreach ($translations as $translation) {
                if ($translation->nid == $tnid) {
                  $nid = $tnid;
                  $langcode = $translation->language;
                  break;
                }
              }
            }
          }
        }

        // If a translation of the node is used and this node is not available in
        // the message buffer, then load this node.
        if ($nid != $context['node']->nid && !isset($messages[$nid][$langcode])) {
          $context['node'] = node_load($nid);
        }
      }

      // Check if this node-language pair has been buffered.
      // If not, build the message and store it for later use.
      if (!isset($messages[$nid][$langcode])) {

        // Use the default theme to render the email content.
        // We temporary clear the $custom_theme to prevent the admin theme
        // from being used when the newsletter is sent from the
        // node add/edit form and the admin theme is other than the
        // default theme. When no $custom_theme is set, the
        // After theming the email $custom_theme is restored.
        global $custom_theme;
        $org_cutom_theme = $custom_theme;
        $custom_theme = '';
        $node = drupal_clone($context['node']);

        // Add simplenews specific header data
        $headers = array_merge($message['headers'], _simplenews_headers($node, $params['from']['address']));
        $headers['From'] = $params['from']['formatted'];
        $message['headers'] = $messages[$nid][$langcode]['headers'] = $headers;

        // Build email subject
        if ($tid = $node->simplenews['tid']) {
          $term = taxonomy_get_term($tid);

          // Translate the newsletter term name if simplenews vocabulary uses Localized terms.
          if (module_exists('i18ntaxonomy') && i18ntaxonomy_vocabulary(variable_get('simplenews_vid', '')) == I18N_TAXONOMY_LOCALIZE) {
            $name = tt('taxonomy:term:' . $tid . ':name', $term->name, $langcode);
          }
          else {
            $name = $term->name;
          }
        }
        else {
          $name = t('Unassigned newsletter');
        }
        $subject = theme('simplenews_newsletter_subject', $name, $node->title, $message['language']);
        $subject = str_replace(array(
          "\r",
          "\n",
        ), '', $subject);
        $message['subject'] = $messages[$nid][$langcode]['subject'] = $subject;

        // Build message body
        // Processing node body mimics node_view() with full node view
        $node->build_mode = 'email_' . $node->simplenews['s_format'];
        $node = node_build_content($node, FALSE, TRUE);
        $content = drupal_render($node->content);
        $node->body = $content;
        unset($node->teaser);

        // Set a flag to prevent token replacement during node alter.
        $node->simplenews_mail = TRUE;
        node_invoke_nodeapi($node, 'alter', FALSE, TRUE);
        unset($node->simplenews_mail);
        $body = theme(array(
          'simplenews_newsletter_body__' . $context['node']->simplenews['tid'],
          'simplenews_newsletter_body',
        ), $node, $message['language']);

        // Buffer body text node and language specific
        $messages[$nid][$langcode]['body'] = $body;

        // Build and buffer message footer
        $footer = theme(array(
          'simplenews_newsletter_footer__' . $context['node']->simplenews['tid'],
          'simplenews_newsletter_footer',
        ), $context['node'], $key, $message['language']);
        $messages[$nid][$langcode]['footer'] = $footer;

        // Restore the custom theme.
        $custom_theme = $org_cutom_theme;
      }
      else {

        // Get message data from buffer
        $message['headers'] = $messages[$nid][$langcode]['headers'];
        $message['subject'] = $messages[$nid][$langcode]['subject'];
        $body = $messages[$nid][$langcode]['body'];
        $footer = $messages[$nid][$langcode]['footer'];
      }

      // Build message body.
      // Replace tokens with user specific data and
      // Convert to plain text if required.
      $variables = simplenews_mail_tokens($context['account'], $context, is_object($context['account']->language) ? $context['account']->language : language_default());
      $body = strtr($body, $variables);
      if ($context['node']->simplenews['s_format'] == 'plain') {
        $body = simplenews_html_to_text($body, variable_get('simplenews_hyperlinks_' . $context['node']->simplenews['tid'], 1));
      }
      $message['body']['body'] = $body;

      // Build message footer.
      // Replace tokens with user specific data
      $message['body']['footer'] = strtr($footer, $variables);

      // Add user specific header data.
      $message['headers']['List-Unsubscribe'] = strtr('<!confirm_unsubscribe_url>', $variables);
      break;
    case 'subscribe':

      // Use formatted from address "name" <mail_address>
      $message['headers']['From'] = $params['from']['formatted'];
      $variables = simplenews_mail_tokens($context['account'], $context, is_object($context['account']->language) ? $context['account']->language : language_default());
      $message['subject'] = _simplenews_subscription_confirmation_text('subscribe_subject', $context['account']->language, $variables);
      if (simplenews_user_is_subscribed($context['account']->mail, $context['newsletter']->tid)) {
        $message['body'] = _simplenews_subscription_confirmation_text('subscribe_subscribed', $context['account']->language, $variables);
      }
      else {
        $message['body'] = _simplenews_subscription_confirmation_text('subscribe_unsubscribed', $context['account']->language, $variables);
      }
      break;
    case 'unsubscribe':

      // Use formatted from address "name" <mail_address>
      $message['headers']['From'] = $params['from']['formatted'];
      $variables = simplenews_mail_tokens($context['account'], $context, is_object($context['account']->language) ? $context['account']->language : language_default());
      $message['subject'] = _simplenews_subscription_confirmation_text('subscribe_subject', $context['account']->language, $variables);
      if (simplenews_user_is_subscribed($context['account']->mail, $context['newsletter']->tid)) {
        $message['body'] = _simplenews_subscription_confirmation_text('unsubscribe_subscribed', $context['account']->language, $variables);
      }
      else {
        $message['body'] = _simplenews_subscription_confirmation_text('unsubscribe_unsubscribed', $context['account']->language, $variables);
      }
      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);
  }
}

/**
 * Send a node to an email address.
 *
 * @param $nid node id of newsletter node
 * @param $vid revision id of newsletter node
 * @param $mail target email address
 * @param $key email key [node|test]
 *
 * @return TRUE if email is succesfully delivered by php mail()
 */
function simplenews_mail_mail($nid, $vid, $mail, $key = 'node') {

  // Get subscription data for recipient and language
  $account = new stdClass();
  $account->mail = $mail;
  $subscription = simplenews_get_subscription($account);
  $params['context']['account'] = $subscription;

  // Get node data for the mail
  $node = node_load($nid, $vid);
  if (is_object($node)) {
    $params['from'] = _simplenews_set_from($node);
    $params['context']['newsletter'] = taxonomy_get_term($node->simplenews['tid']);
    $params['context']['node'] = $node;

    // Send mail
    if (module_exists('mimemail')) {

      // If mimemail module is installed ALL emails are send via this module.
      // drupal_mail() builds the content of the email but does NOT send.
      $message = drupal_mail('simplenews', $key, $subscription->mail, $subscription->language, $params, $params['from']['formatted'], FALSE);
      $to = isset($message['params']['context']['account']) ? $message['params']['context']['account'] : $message['to'];
      $plain = $message['params']['context']['node']->simplenews['s_format'] == 'plain' ? TRUE : NULL;
      $mimemail_result = mimemail($message['from'], $to, $message['subject'], $message['body'], $plain, $message['headers'], $plain ? $message['body'] : simplenews_html_to_text($message['body'], TRUE), isset($message['params']['context']['node']->files) ? $message['params']['context']['node']->files : array(), $message['id']);

      // Mimemail has changed its API (see http://drupal.org/node/808518) but we keep backward compatibility
      if (is_array($mimemail_result)) {
        $message = $mimemail_result;
      }
      else {
        $message['result'] = $mimemail_result;
      }
    }
    else {
      $message = drupal_mail('simplenews', $key, $subscription->mail, $subscription->language, $params, $params['from']['formatted'], TRUE);
    }

    // Log sent result in watchdog.
    if (variable_get('simplenews_debug', FALSE)) {
      $via_mimemail = '';
      if (module_exists('mimemail')) {
        $via_mimemail = t('Sent via Mime Mail');
      }

      //TODO Add line break before %mimemail.
      if ($message['result']) {
        watchdog('simplenews', 'Outgoing email. Message type: %type<br />Subject: %subject<br />Recipient: %to %mimemail', array(
          '%type' => $key,
          '%to' => $message['to'],
          '%subject' => $message['subject'],
          '%mimemail' => $via_mimemail,
        ), WATCHDOG_DEBUG);
      }
      else {
        watchdog('simplenews', 'Outgoing email failed. Message type: %type<br />Subject: %subject<br />Recipient: %to %mimemail', array(
          '%type' => $key,
          '%to' => $message['to'],
          '%subject' => $message['subject'],
          '%mimemail' => $via_mimemail,
        ), WATCHDOG_ERROR);
      }
    }

    // Build array of sent results for spool table and reporting.
    if ($message['result']) {
      $message['result'] = array(
        'status' => SIMPLENEWS_SPOOL_DONE,
        'error' => FALSE,
      );
    }
    else {

      // This error may be caused by faulty mailserver configuration or overload.
      // Mark "pending" to keep trying.
      $message['result'] = array(
        'status' => SIMPLENEWS_SPOOL_PENDING,
        'error' => TRUE,
      );
    }
  }
  else {

    // Node could not be loaded. The node is probably deleted while pending to be sent.
    // This error is not recoverable, mark "done".
    $message['result'] = array(
      'status' => SIMPLENEWS_SPOOL_DONE,
      'error' => TRUE,
    );
    watchdog('simplenews', 'Newsletter not send: newsletter issue does not exist (nid = @nid; vid = @vid).', array(
      '@nid' => $message['nid'],
      '@vid' => $message['vid'],
    ), WATCHDOG_ERROR);
  }
  return isset($message['result']) ? $message['result'] : FALSE;
}

/**
 * Send simplenews newsletters from the spool.
 *
 * Individual newsletter emails are stored in database spool.
 * Sending is triggered by cron or immediately when the node is saved.
 * Mail data is retreived from the spool, rendered and send one by one
 * If sending is succesful the message is marked as send in the spool.
 */
function simplenews_mail_spool($nid = NULL, $vid = NULL, $limit = NULL) {

  // Send pending messages from database cache
  // A limited number of mails is retrieved from the spool
  $limit = isset($limit) ? $limit : variable_get('simplenews_throttle', 20);
  if ($messages = simplenews_get_spool(SIMPLENEWS_SPOOL_PENDING, $nid, $vid, $limit)) {
    $count_fail = $count_success = 0;

    // Get PHP maximum execution time. 30 seconds default.
    $max_execution_time = ini_get('max_execution_time') ? ini_get('max_execution_time') : SIMPLENEWS_MAX_EXECUTION_TIME;
    _simplenews_measure_usec(TRUE);
    foreach ($messages as $key => $message) {
      $result = simplenews_mail_mail($message['nid'], $message['vid'], $message['mail']);

      // Update spool status.
      // This is not optimal for performance but prevents duplicate emails
      // in case of PHP execution time overrun.
      simplenews_update_spool(array(
        $key,
      ), $result);
      if ($result['status'] == SIMPLENEWS_SPOOL_DONE) {
        $count_success++;
      }
      if ($result['error']) {
        $count_fail++;
      }

      // Check every n emails if we exceed the limit.
      // When PHP maximum execution time is almost elapsed we interrupt
      // sending. The remainder will be sent during the next cron run.
      if (++$check_counter >= SIMPLENEWS_SEND_CHECK_INTERVAL) {
        $check_counter = 0;

        // Break the sending if a percentage of max execution time was exceeded.
        $elapsed = _simplenews_measure_usec();
        if ($elapsed > SIMPLENEWS_SEND_TIME_LIMIT * $max_execution_time) {
          watchdog('simplenews', 'Sending interrupted: PHP maximum execution time almost exceeded. Remaining newsletters will be sent during the next cron run. If this warning occurs regularly you should reduce the !cron_throttle_setting.', array(
            '!cron_throttle_setting' => l(t('Cron throttle setting'), 'admin/settings/simplenews/mail'),
          ), WATCHDOG_WARNING);
          break;
        }
      }
    }

    // Report sent result and elapsed time. On Windows systems retrusage() is
    // not implemented and hence no elapsed time is available.
    if (function_exists('getrusage')) {
      watchdog('simplenews', '%success emails sent in %sec seconds, %fail failed sending.', array(
        '%success' => $count_success,
        '%sec' => round(_simplenews_measure_usec(), 1),
        '%fail' => $count_fail,
      ));
    }
    else {
      watchdog('simplenews', '%success emails sent, %fail failed.', array(
        '%success' => $count_success,
        '%fail' => $count_fail,
      ));
    }
  }
}

/**
 * Save mail message in mail cache table.
 *
 * @param array $message data array to be stored
 *  $message['mail']
 *  $message['nid']
 *  $message['vid']
 *  $message['tid']
 *  $message['status']  (Default: 1 = pending)
 *  $message['time']    (default: current unix timestamp)
 * @param array $message Mail message array
 */
function simplenews_save_spool($message) {
  $status = isset($message['status']) ? $message['status'] : SIMPLENEWS_SPOOL_PENDING;
  $time = isset($message['time']) ? $message['time'] : time();
  db_query("INSERT INTO {simplenews_mail_spool} (mail, nid, vid, tid, status, timestamp)\n            VALUES ('%s', %d, %d, %d, %d, %d)", $message['mail'], $message['nid'], $message['vid'], $message['tid'], $status, $time);
}

/**
 * Retreive data from mail spool
 *
 * @param string  $status Status of data to be retreived (0 = hold, 1 = pending, 2 = send)
 * @param integer $nid node id
 * @param integer $vid node version id
 * @param integer $limit The maximum number of mails to load from the spool
 *
 * @return array Mail message array
 *  $message['msid']
 *  $message['mail']
 *  $message['nid']
 *  $message['vid']
 *  $message['tid']
 *  $message['status']
 *  $message['time']
 */
function simplenews_get_spool($status, $nid = NULL, $vid = NULL, $limit = 999999) {
  $messages = array();
  $result = db_query_range("SELECT * FROM {simplenews_mail_spool} s WHERE s.status = %d ORDER BY s.timestamp ASC", $status, 0, $limit);
  while ($data = db_fetch_array($result)) {
    $message = array();
    foreach ($data as $key => $value) {
      $message[$key] = $value;
    }
    $messages[$data['msid']] = $message;
  }
  return $messages;
}

/**
 * Update status of mail data in spool table.
 *
 * Time stamp is set to current time.
 *
 * @param array $msids
 *   Mail spool id of record to be updated
 * @param array $result
 *   Array containing email sent result
 *    'status' => (0 = hold, 1 = pending, 2 = send)
 *    'error' => error id (optional; defaults to '')
 */
function simplenews_update_spool($msids, $result) {
  $params[] = $result['status'];
  $params[] = isset($result['error']) ? $result['error'] : FALSE;
  $params[] = time();
  $params = array_merge($params, $msids);
  db_query("UPDATE {simplenews_mail_spool} SET status = %d, error = %d, timestamp = %d WHERE msid IN(" . db_placeholders($msids, 'int') . ")", $params);
}

/**
 * Count data in mail spool table.
 *
 * @param integer $nid newsletter node id
 * @param integer $vid newsletter revision id
 * @param string $status email sent status
 *
 * @return array Mail message array
 */
function simplenews_count_spool($nid, $vid, $status = SIMPLENEWS_SPOOL_PENDING) {
  return db_result(db_query("SELECT COUNT(*) FROM {simplenews_mail_spool} WHERE nid = %d AND vid = %d AND status = %d", $nid, $vid, $status));
}

/**
 * Remove records from mail spool table.
 *
 * All records with status 'send' and time stamp before the expiration date
 * are removed from the spool.
 */
function simplenews_clear_spool() {
  $expiration_time = time() - variable_get('simplenews_spool_expire', 0) * 86400;
  db_query("DELETE FROM {simplenews_mail_spool} WHERE status = %d AND timestamp <= %d", SIMPLENEWS_SPOOL_DONE, $expiration_time);
}

/**
 * Update newsletter sent status.
 *
 * Set newsletter sent status based on email sent status in spool table.
 * Translated and untranslated nodes get a different treatment.
 *
 * The spool table holds data for emails to be sent and (optionally)
 * already send emails. The simplenews_newsletters table contains the overall
 * sent status of each newsletter issue (node).
 * Newsletter issues get the status pending when sending is innitiated. As
 * long as unsend emails exist in the spool, the status of the newsletter remains
 * unsend. When no pending emails are found the newsletter status is set 'send'.
 *
 * Translated newsletters are a group of nodes that share the same tnid ({node}.tnid).
 * Only one node of the group is found in the spool, but all nodes should share
 * the same state. Therefore they are checked for the combined number of emails
 * in the spool.
 */
function simplenews_send_status_update() {
  $counts = array();

  // number pending of emails in the spool
  $sum = array();

  // sum of emails in the spool per tnid (translation id)
  $send = array();

  // nodes with the status 'send'
  // For each pending newsletter count the number of pending emails in the spool.
  $result = db_query("SELECT s.nid, s.vid, s.tid, n.tnid FROM {simplenews_newsletters} s JOIN {node} n ON s.nid = n.nid AND s.vid = n.vid WHERE s.s_status = %d", SIMPLENEWS_STATUS_SEND_PENDING);
  while ($newsletter = db_fetch_object($result)) {

    // nid-vid are combined in one unique key.
    $counts[$newsletter->tnid][$newsletter->nid . '-' . $newsletter->vid] = simplenews_count_spool($newsletter->nid, $newsletter->vid);
  }

  // Determine which nodes are send per translation group and per individual node.
  foreach ($counts as $tnid => $node_count) {

    // The sum of emails per tnid is the combined status result for the group of translated nodes.
    // Untranslated nodes have tnid == 0 which will be ignored later.
    $sum[$tnid] = array_sum($node_count);
    foreach ($node_count as $nidvid => $count) {

      // Translated nodes (tnid != 0)
      if ($tnid != '0' && $sum[$tnid] == '0') {
        $send[] = $nidvid;
      }
      elseif ($tnid == '0' && $count == '0') {
        $send[] = $nidvid;
      }
    }
  }

  // Update overall newsletter status
  if (!empty($send)) {
    foreach ($send as $nidvid) {

      // Split the combined key 'nid-vid'
      $nid = strtok($nidvid, '-');
      $vid = strtok('-');
      db_query("UPDATE {simplenews_newsletters} SET s_status = '%s' WHERE nid = %d AND vid = %d", SIMPLENEWS_STATUS_SEND_READY, $nid, $vid);
    }
  }
}

/**
 * Implementation of hook_views_api().
 */
function simplenews_views_api() {
  return array(
    'api' => 2,
  );
}

/**
 * Call simplenews actions.
 */
function simplenews_call_actions($op, $subscription) {

  // Only call actions when the simplenews_action module is enabled.
  if (!module_exists('simplenews_action')) {
    return;
  }
  $aids = _trigger_get_hook_aids('simplenews', $op);
  $context = array(
    'hook' => 'simplenews',
    'op' => $op,
    'account' => $subscription,
  );
  foreach ($aids as $aid => $action_info) {
    actions_do($aid, $subscription, $context);
  }
}

/**
 * Build formatted from-name and email for a mail object.
 *
 * Each newsletter (serie; tid) can have a different from address.
 * The from name and address depend on the newsletter term tid which is included in the $node object
 *
 * @param object $node Node object of a simplenews newsletter
 *
 * @return array [address] = from address; [formatted] = formatted from name and address
 */
function _simplenews_set_from($node = NULL) {
  $address_default = variable_get('site_mail', ini_get('sendmail_from'));
  $name_default = variable_get('site_name', 'Drupal');
  if (isset($node->simplenews['tid'])) {
    $address = variable_get('simplenews_from_address_' . $node->simplenews['tid'], variable_get('simplenews_from_address', $address_default));
    $name = variable_get('simplenews_from_name_' . $node->simplenews['tid'], variable_get('simplenews_from_name', $name_default));
  }
  else {
    $address = variable_get('simplenews_from_address', $address_default);
    $name = variable_get('simplenews_from_name', $name_default);
  }

  // Windows based PHP systems don't accept formatted emails.
  $formatted_address = drupal_substr(PHP_OS, 0, 3) == 'WIN' ? $address : '"' . addslashes(mime_header_encode($name)) . '" <' . $address . '>';
  return array(
    'address' => $address,
    'formatted' => $formatted_address,
  );
}

/**
 * Build header array with priority and receipt confirmation settings.
 *
 * @param $node: node object
 * @param $from: from email address
 *
 * @return Header array with priority and receipt confirmation info
 */
function _simplenews_headers($node, $from) {
  $headers = array();

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

  // Add priority if set.
  switch ($node->simplenews['priority']) {
    case SIMPLENEWS_PRIORITY_HIGHEST:
      $headers['Priority'] = 'High';
      $headers['X-Priority'] = '1';
      $headers['X-MSMail-Priority'] = 'Highest';
      break;
    case SIMPLENEWS_PRIORITY_HIGH:
      $headers['Priority'] = 'urgent';
      $headers['X-Priority'] = '2';
      $headers['X-MSMail-Priority'] = 'High';
      break;
    case SIMPLENEWS_PRIORITY_NORMAL:
      $headers['Priority'] = 'normal';
      $headers['X-Priority'] = '3';
      $headers['X-MSMail-Priority'] = 'Normal';
      break;
    case SIMPLENEWS_PRIORITY_LOW:
      $headers['Priority'] = 'non-urgent';
      $headers['X-Priority'] = '4';
      $headers['X-MSMail-Priority'] = 'Low';
      break;
    case SIMPLENEWS_PRIORITY_LOWEST:
      $headers['Priority'] = 'non-urgent';
      $headers['X-Priority'] = '5';
      $headers['X-MSMail-Priority'] = 'Lowest';
      break;
  }

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

/**
 * HTML to text conversion for HTML and special characters.
 *
 * Converts some special HTMLcharacters in addition to drupal_html_to_text()
 *
 * @param string $text Source text with HTML and special characters
 * @param boolean $inline_hyperlinks
 *   TRUE: URLs will be placed inline.
 *   FALSE: URLs will be converted to numbered reference list.
 * @return string Target text with HTML and special characters replaced
 */
function simplenews_html_to_text($text, $inline_hyperlinks = TRUE) {

  // By replacing <a> tag by only its URL the URLs will be placed inline
  // in the email body and are not converted to a numbered reference list
  // by drupal_html_to_text().
  // URL are converted to abolute URL as drupal_html_to_text() would have.
  if ($inline_hyperlinks) {
    $pattern = '@<a[^>]+?href="([^"]*)"[^>]*?>(.+?)</a>@is';
    $text = preg_replace_callback($pattern, '_simplenews_absolute_mail_urls', $text);
  }

  // Replace some special characters before performing the drupal standard conversion.
  $preg = _simplenews_html_replace();
  $text = preg_replace(array_keys($preg), array_values($preg), $text);

  // Perform standard drupal html to text conversion.
  return drupal_html_to_text($text);
}

/**
 * Helper function for simplenews_html_to_text().
 *
 * Replaces URLs with abolute URLs.
 */
function _simplenews_absolute_mail_urls($match) {
  global $base_url, $base_path;
  static $regexp;
  $url = $label = '';
  if ($match) {
    if (empty($regexp)) {
      $regexp = '@^' . preg_quote($base_path, '@') . '@';
    }
    list(, $url, $label) = $match;
    $url = strpos($url, '://') ? $url : preg_replace($regexp, $base_url . '/', $url);

    // If the link is formed by Drupal's URL filter, we only return the URL.
    // The URL filter generates a label out of the original URL.
    if (strpos($label, '...') === strlen($label) - 3) {

      // Remove ellipsis from end of label.
      $label = substr($label, 0, strlen($label) - 3);
    }
    if (strpos($url, $label) !== FALSE) {
      return $url;
    }
    return $label . ' ' . $url;
  }
}

/**
 * Helper function for simplenews_html_to_text().
 *
 * List of preg* regular expression patterns to search for and replace with
 */
function _simplenews_html_replace() {
  return array(
    '/&quot;/i' => '"',
    '/&gt;/i' => '>',
    '/&lt;/i' => '<',
    '/&amp;/i' => '&',
    '/&copy;/i' => '(c)',
    '/&trade;/i' => '(tm)',
    '/&#8220;/' => '"',
    '/&#8221;/' => '"',
    '/&#8211;/' => '-',
    '/&#8217;/' => "'",
    '/&#38;/' => '&',
    '/&#169;/' => '(c)',
    '/&#8482;/' => '(tm)',
    '/&#151;/' => '--',
    '/&#147;/' => '"',
    '/&#148;/' => '"',
    '/&#149;/' => '*',
    '/&reg;/i' => '(R)',
    '/&bull;/i' => '*',
    '/&euro;/i' => 'Euro ',
  );
}

/**
 * Build array of mail tokens.
 *
 * Depending on the context in which this function is called, some tokens
 * may return an empty value. e.g. !newsletter_url is empty when called
 * while building a confirmation email.
 *
 * @param $subscription
 *  Subscription or user object
 * @param $context
 *  [newsletter] newsletter term object
 *  [node] node object
 * @param $language
 *  Language object
 *
 * @return array of tokens and token values.
 */
function simplenews_mail_tokens($subscription, $context, $language) {

  // Build hash for (un)subscription confirmation link for subscribed user.
  $hash = '';
  if (isset($subscription->snid) && isset($context['newsletter']->tid)) {
    $hash = _simplenews_generate_hash($subscription->mail, $subscription->snid, $context['newsletter']->tid);
  }

  // Get newsletter name if not in newsletter object.
  $name = isset($context['newsletter']->name) ? $context['newsletter']->name : '';
  if (!$name) {
    if (isset($context['node']->simplenews['tid'])) {
      if ($term = taxonomy_get_term($context['node']->simplenews['tid'])) {

        // Translate newsletter name if required.
        $name = $term->name;
        if (module_exists('i18ntaxonomy') && i18ntaxonomy_vocabulary(variable_get('simplenews_vid', '')) == I18N_TAXONOMY_LOCALIZE) {
          $name = tt('taxonomy:term:' . $context['node']->simplenews['tid'] . ':name', $name, $language->language);
        }
      }
    }
  }

  // Get tokens from user_mail_tokens() and add simplenews variables.
  if ($subscription->uid) {
    $vars = user_mail_tokens($subscription, $language);
  }
  else {
    $vars = array();
    $vars['!site'] = variable_get('site_name', 'Drupal');
    $vars['!mailto'] = isset($subscription->mail) ? $subscription->mail : '';
    $vars['!date'] = format_date(time(), 'medium', '', NULL, $language->language);
    $vars['!login_uri'] = url('user', array(
      'absolute' => TRUE,
      'language' => $language,
    ));
  }

  // Replace existing uri and uri_brief to cope with multilingual sites.
  $vars['!uri'] = url('', array(
    'absolute' => TRUE,
    'language' => $language,
  ));
  $vars['!uri_brief'] = preg_replace('!^https?://!', '', $vars['!uri']);
  $vars['!confirm_subscribe_url'] = url('newsletter/confirm/add/' . $hash, array(
    'absolute' => TRUE,
    'language' => $language,
  ));
  $vars['!confirm_unsubscribe_url'] = url('newsletter/confirm/remove/' . $hash, array(
    'absolute' => TRUE,
    'language' => $language,
  ));
  $vars['!newsletter_url'] = isset($context['node']->nid) ? url('node/' . $context['node']->nid, array(
    'absolute' => TRUE,
    'language' => $language,
  )) : '';
  $vars['!newsletter_name'] = $name;
  return $vars;
}

/**
 * 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;
}

/**
 * Implementation of hook_help().
 */
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 by a <strong>newsletter taxonomy term</strong>. Node type and vocabulary 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 at subscription and unsubscription. 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/user/permissions', array(
          'fragment' => 'module-simplenews',
        )) . "</li>\n";
      }
      if (user_access('administer simplenews settings')) {
        $help .= "<li>" . l(t('Configure Simplenews'), 'admin/settings/simplenews') . "</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/build/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/content/simplenews/types'),
          '@sent' => url('admin/content/simplenews'),
          '@subscriptions' => url('admin/content/simplenews/users'),
        )) . "</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. Send a newsletter or a test newsletter by selecting the appropriate radio button and submitting the node.') . "</li>\n";
      if (user_access('administer simplenews settings')) {
        $help .= "<li>" . t('Set default send options at <a href="@configuration">Administer > Site configuration > Simplenews > Newsletter</a>.', array(
          '@configuration' => url('admin/settings/simplenews/newsletter'),
        )) . "</li>\n";
      }
      if (user_access('administer newsletters')) {
        $help .= "<li>" . t('Set newsletter specific options at <a href="@configuration">Administer > Content management > Newsletters > Newsletters</a>.', array(
          '@configuration' => url('admin/content/simplenews/types'),
        )) . "</li>\n";
      }
      $help .= "</ul>";
      return $help;
    case 'admin/settings/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/content/simplenews/types'),
      )) . "</li>\n";
      if (!module_exists('mimemail')) {
        $help .= "<li>" . t('Install <a href="@mime_mail_url">Mime Mail</a> to send HTML emails or emails with attachments (both plain text and HTML).', array(
          '@mime_mail_url' => 'http://drupal.org/project/mimemail',
        )) . "</li>\n";
      }
      $help .= "</ul>";
      return $help;
    case 'admin/content/simplenews/types/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/content/node-type/simplenews/display/simplenews':
      $help = '<p>' . t("'Plain' display settings apply to the content of emails sent in plain text format. 'HTML' display settings apply to both HTML and plain text alternative content of emails sent in HTML format.") . "</p>\n";
      return $help;
  }
}

/**
 * Flatten a nested array
 */
function _simplenews_flatten_array($array) {
  $return = array();
  foreach ($array as $key => $value) {
    if (is_array($value)) {
      $return += _simplenews_flatten_array($value);
    }
    else {
      $return[$key] = $value;
    }
  }
  return $return;
}

/**
 * Generate the hash key used for subscribe/unsubscribe link.
 */
function _simplenews_generate_hash($mail, $snid, $tid) {
  return drupal_substr(md5($mail . simplenews_private_key()), 0, 10) . $snid . 't' . $tid;
}

/**
 * Determine possible mail format options.
 *
 * The mime_mail module must be installed to send HTML mails.
 */
function _simplenews_format_options() {
  $options = array(
    'plain' => t('plain'),
  );
  if (module_exists('mimemail') || module_exists('htmlmail')) {
    $options['html'] = t('html');
  }
  return $options;
}

/**
 * Generate default and custom subscription confirmation email text.
 *
 * @param string $key text identification key
 * @param object $language language object
 * @param array $variables array of variables as used by t()
 */
function _simplenews_subscription_confirmation_text($key, $language = NULL, $variables = array()) {
  $langcode = isset($language) ? $language->language : NULL;
  if ($admin_setting = variable_get('simplenews_confirm_' . $key, FALSE)) {

    // An admin setting overrides the default string.
    return strtr($admin_setting, $variables);
  }
  else {

    // No override, return default string.
    switch ($key) {
      case 'subscribe_unsubscribed':
        return t("We have received a request for subscription of !mailto to the !newsletter_name on !site website at !uri. To confirm this subscription please use the link below.\n\n!confirm_subscribe_url", $variables, $langcode);
      case 'subscribe_subscribed':
        return t("We have received a request for subscription of !mailto to the !newsletter_name on !site website at !uri. However, this email is already subscribed to this newsletter. If you intended to unsubscribe please visit our site: !uri", $variables, $langcode);
      case 'unsubscribe_subscribed':
        return t("We have received a request to unsubscribe !mailto from the !newsletter_name on !site website at !uri. To confirm this unsubscription please use the link below.\n\n!confirm_unsubscribe_url", $variables, $langcode);
      case 'unsubscribe_unsubscribed':
        return t("We have received a request to unsubscribe !mailto from the !newsletter_name on !site website at !uri. However, this email is not subscribed to this newsletter. If you intended to subscribe please visit our site: !uri", $variables, $langcode);
      case 'subscribe_subject':
        return t("Confirmation for !newsletter_name from !site", $variables, $langcode);
    }
  }
}

/**
 * Helper function to measure PHP execution time in microseconds.
 *
 * @param bool $start TRUE reset the time and start counting.
 * @return float: elapsed PHP execution time since start.
 */
function _simplenews_measure_usec($start = FALSE) {

  // Windows systems don't implement getrusage(). There is no alternative.
  if (!function_exists('getrusage')) {
    return 0;
  }
  static $start_time;
  $usage = getrusage();
  $now = (double) ($dat["ru_stime.tv_sec"] . '.' . $dat["ru_stime.tv_usec"]) + (double) ($usage["ru_utime.tv_sec"] . '.' . $usage["ru_utime.tv_usec"]);
  if ($start) {
    $start_time = $now;
    return 0;
  }
  return $now - $start_time;
}

/**
 * Implementation of hook_content_build_modes().
 */
function simplenews_content_build_modes() {
  return array(
    'simplenews' => array(
      'title' => t('Simplenews'),
      'build modes' => array(
        'email_plain' => array(
          'title' => t('Email: Plain'),
          'views style' => FALSE,
        ),
        'email_html' => array(
          'title' => t('Email: HTML'),
          'views style' => FALSE,
        ),
      ),
    ),
  );
}

/**
 * Implementation of hook_theme().
 */
function simplenews_theme() {
  return array(
    'simplenews_block' => array(
      'template' => 'simplenews-block',
      'arguments' => array(
        'tid' => NULL,
      ),
      'pattern' => 'simplenews_block__',
    ),
    'simplenews_status' => array(
      'template' => 'simplenews-status',
      'file' => 'simplenews.admin.inc',
      'arguments' => array(
        'status' => NULL,
        'source' => NULL,
      ),
    ),
    'simplenews_newsletter_subject' => array(
      'arguments' => array(
        'name' => NULL,
        'title' => NULL,
        'language' => NULL,
      ),
    ),
    'simplenews_newsletter_body' => array(
      'template' => 'simplenews-newsletter-body',
      'arguments' => array(
        'node' => NULL,
        'language' => NULL,
      ),
      'pattern' => 'simplenews_newsletter_body__',
    ),
    'simplenews_newsletter_footer' => array(
      'template' => 'simplenews-newsletter-footer',
      'arguments' => array(
        'node' => NULL,
        'key' => NULL,
        'language' => NULL,
      ),
      'pattern' => 'simplenews_newsletter_footer__',
    ),
    'simplenews_subscription_list' => array(
      'file' => 'simplenews.admin.inc',
      'arguments' => array(
        'form' => NULL,
      ),
    ),
  );
}

/**
 * Process variables to format the simplenews block.
 *
 * Collect data and apply access restrictions.
 *
 * $variables contains:
 * - $tid
 *
 * @see simplenews-block.tpl.php
 * @see theme_simplenews-block()
 */
function template_preprocess_simplenews_block(&$variables) {
  global $user;
  $tid = $variables['tid'];

  // 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_' . $tid, t('Stay informed on our latest news!')));
  if (user_access('subscribe to newsletters')) {
    $variables['form'] = drupal_get_form('simplenews_block_form_' . $tid);
    $variables['subscription_link'] = l(t('Manage my subscriptions'), 'newsletter/subscriptions');
  }
  $variables['newsletter_link'] = l(t('Previous issues'), 'taxonomy/term/' . $tid);
  $recent = simplenews_recent_newsletters($tid, variable_get('simplenews_block_i_' . $tid, 5));
  $variables['issue_list'] = theme('item_list', $recent, t('Previous issues'), 'ul');
  $term = taxonomy_get_term($tid);
  $variables['rssfeed'] = theme('feed_icon', url('taxonomy/term/' . $tid . '/0/feed'), t('@newsletter feed', array(
    '@newsletter' => $term->name,
  )));

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

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

/**
 * Theme the newsletter email subject.
 */
function theme_simplenews_newsletter_subject($name, $title, $language) {
  return '[' . $name . '] ' . $title;
}

/**
 * Process variables to format the simplenews newsletter body.
 *
 * $variables contains:
 * - $node
 * - $language
 *
 * @see simplenews-newsletter-body.tpl.php
 */
function template_preprocess_simplenews_newsletter_body(&$variables) {
  $variables['title'] = check_plain($variables['node']->title);
  $variables['body'] = $variables['node']->body;
}

/**
 * Process variables to format the simplenews newsletter footer.
 *
 * $variables contains:
 * - $node
 * - $key
 * - $language
 *
 * @see simplenews-newsletter-footer.tpl.php
 */
function template_preprocess_simplenews_newsletter_footer(&$variables) {
  $variables['format'] = $variables['node']->simplenews['s_format'];
  $variables['unsubscribe_text'] = t('Unsubscribe from this newsletter', array(), $variables['language']->language);
  $variables['test_message'] = t('This is a test version of the newsletter.', array(), $variables['language']->language);
}

Functions

Namesort descending Description
simplenews_block Implementation of hook_block().
simplenews_block_form Newsletter (un)subscription form for authenticated and anonymous users.
simplenews_block_form_submit
simplenews_block_form_validate
simplenews_call_actions Call simplenews actions.
simplenews_clear_spool Remove records from mail spool table.
simplenews_content_build_modes Implementation of hook_content_build_modes().
simplenews_count_spool Count data in mail spool table.
simplenews_cron Implementation of hook_cron().
simplenews_delete_subscription Delete every subscription for the given subscription ID.
simplenews_forms Implementation of hook_forms().
simplenews_form_alter Implementation of hook_form_alter().
simplenews_get_spool Retreive data from mail spool
simplenews_get_subscription Get the subscription object for the given account.
simplenews_help Implementation of hook_help().
simplenews_html_to_text HTML to text conversion for HTML and special characters.
simplenews_init Implementation of hook_init().
simplenews_mail Implementation of hook_mail().
simplenews_mail_mail Send a node to an email address.
simplenews_mail_spool Send simplenews newsletters from the spool.
simplenews_mail_tokens Build array of mail tokens.
simplenews_menu Implementation of hook_menu().
simplenews_newsletter_access Menu item access callback.
simplenews_nodeapi Implementation of hook_nodeapi().
simplenews_node_type Implementation of hook_node_type().
simplenews_perm Implementation of hook_perm().
simplenews_private_key Create a 32 character identifier.
simplenews_recent_newsletters Create a list of recent newsletters.
simplenews_save_spool Save mail message in mail cache table.
simplenews_send_node Send newsletter node to subcribers.
simplenews_send_status_update Update newsletter sent status.
simplenews_send_test Send test version of newsletter.
simplenews_subscribe_user Subscribe a user to a newsletter or send a confirmation mail.
simplenews_subscription_edit_access Access callback for user newsletter editing.
simplenews_taxonomy Implementation of hook_taxonomy().
simplenews_theme Implementation of hook_theme().
simplenews_unsubscribe_user Unsubscribe a user from a newsletter or send a confirmation mail.
simplenews_update_spool Update status of mail data in spool table.
simplenews_user Implementation of hook_user().
simplenews_user_is_subscribed Check if the email address is subscribed to the given newsletter.
simplenews_validate_taxonomy Validate if selected terms are Newsletter taxonomy terms.
simplenews_views_api Implementation of hook_views_api().
template_preprocess_simplenews_block Process variables to format the simplenews 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_newsletter_subject Theme the newsletter email subject.
_simplenews_absolute_mail_urls Helper function for simplenews_html_to_text().
_simplenews_flatten_array Flatten a nested array
_simplenews_format_options Determine possible mail format options.
_simplenews_generate_hash Generate the hash key used for subscribe/unsubscribe link.
_simplenews_headers Build header array with priority and receipt confirmation settings.
_simplenews_html_replace Helper function for simplenews_html_to_text().
_simplenews_measure_usec Helper function to measure PHP execution time in microseconds.
_simplenews_set_from Build formatted from-name and email for a mail object.
_simplenews_subscription_confirmation_text Generate default and custom subscription confirmation email text.
_simplenews_subscription_manager_form Build subscription manager form.
_simplenews_user_load Load a user or creates a dummy anonymous user.

Constants