You are here

simplenews.module in Simplenews 6.2

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

TODO: rework tnid conditions. If nodetype translatable, tnid=0 as long as there's no translation present.

File

simplenews.module
View source
<?php

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

/**
 * @file
 * Simplenews node handling, sent email, newsletter block and general hooks
 *
 * TODO: rework tnid conditions. If nodetype translatable, tnid=0 as long as there's no translation present.
 *
 * @ingroup simplenews
 */

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

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

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

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

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

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

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

/**
 * CAPTURE THE PHP MAX EXECUTION 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 Management',
    'description' => 'Manage newsletters and subscriptions.',
    'type' => MENU_NORMAL_ITEM,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_news',
    ),
    'access callback' => 'simplenews_newsletter_access',
    'file' => 'includes/simplenews.admin.inc',
  );
  $items['admin/content/simplenews/sent'] = array(
    'title' => 'Issues',
    'description' => 'List of newsletter issues.',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'access callback' => 'user_access',
    'access arguments' => array(
      'administer newsletters',
    ),
    'weight' => -10,
  );
  $items['admin/content/simplenews/types'] = array(
    'title' => 'Newsletters',
    'description' => 'List, add and edit newsletter series.',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'simplenews_types_overview',
    'access callback' => 'user_access',
    'access arguments' => array(
      'administer newsletters',
    ),
    'file' => 'includes/simplenews.admin.inc',
    'weight' => -8,
  );
  $items['admin/content/simplenews/types/edit/%'] = array(
    'type' => MENU_CALLBACK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_types_form',
      5,
    ),
    'access arguments' => array(
      'administer newsletters',
    ),
    'file' => 'includes/simplenews.admin.inc',
  );
  $items['admin/content/simplenews/types/delete/%'] = array(
    'type' => MENU_CALLBACK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_types_delete',
      5,
    ),
    'access arguments' => array(
      'administer newsletters',
    ),
    'file' => 'includes/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' => 'includes/simplenews.admin.inc',
    'weight' => -9,
  );
  $items['admin/content/simplenews/subscriptions/delete'] = array(
    'type' => MENU_CALLBACK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_subscription_multiple_delete_confirm',
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'administer simplenews subscriptions',
    ),
    'file' => 'includes/simplenews.admin.inc',
  );
  $items['admin/content/simplenews/users'] = array(
    'title' => 'Subscriptions',
    'description' => 'Newsletter subscription management.',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_subscription_admin',
    ),
    'access callback' => 'user_access',
    'access arguments' => array(
      'administer simplenews subscriptions',
    ),
    'file' => 'includes/simplenews.admin.inc',
    'weight' => -7,
  );
  $items['admin/content/simplenews/users/edit/%'] = array(
    'type' => MENU_CALLBACK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_subscriptions_admin_form',
      5,
    ),
    'access arguments' => array(
      'administer simplenews subscriptions',
    ),
    'file' => 'includes/simplenews.subscription.inc',
  );
  $items['admin/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' => 'includes/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' => 'includes/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' => 'includes/simplenews.admin.inc',
    'weight' => -8,
  );
  $items['admin/settings/simplenews'] = array(
    'title' => 'Simplenews settings',
    'description' => 'Manage simplenews configuration.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_settings',
    ),
    'access arguments' => array(
      'administer simplenews settings',
    ),
    'file' => 'includes/simplenews.admin.inc',
  );
  $items['admin/settings/simplenews/general'] = array(
    'title' => 'General',
    'description' => 'Simplenews content type and vocabulary settings.',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/settings/simplenews/newsletter'] = array(
    'title' => 'Defaults',
    'description' => 'Newsletter default settings and sender data.',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_settings_newsletter',
    ),
    'access arguments' => array(
      'administer simplenews settings',
    ),
    'file' => 'includes/simplenews.admin.inc',
    'weight' => -9,
  );
  $items['admin/settings/simplenews/subscription'] = array(
    'title' => 'Subscriptions',
    'description' => 'Subscription settings, opt-in/out confirmation email text.',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_settings_subscription',
    ),
    'access arguments' => array(
      'administer simplenews settings',
    ),
    'file' => 'includes/simplenews.admin.inc',
    'weight' => -8,
  );
  $items['admin/settings/simplenews/mail'] = array(
    'title' => 'Send mail',
    'description' => 'Send mail, cron and debug options.',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_admin_settings_mail',
    ),
    'access arguments' => array(
      'administer simplenews settings',
    ),
    'file' => 'includes/simplenews.admin.inc',
    'weight' => -7,
  );
  $items['newsletter/confirm'] = array(
    'type' => MENU_CALLBACK,
    'page callback' => 'simplenews_confirm_subscription',
    'access callback' => TRUE,
    'file' => 'includes/simplenews.subscription.inc',
  );
  $items['newsletter/subscriptions'] = array(
    'title' => 'Newsletter subscriptions',
    'type' => MENU_CALLBACK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'simplenews_subscriptions_page_form',
    ),
    'access arguments' => array(
      'subscribe to newsletters',
    ),
    'file' => 'includes/simplenews.subscription.inc',
  );
  $items['node/%node/simplenews'] = array(
    'title' => 'Newsletter',
    'type' => MENU_LOCAL_TASK,
    'access callback' => 'simplenews_node_tab_access',
    'access arguments' => array(
      1,
    ),
    'page callback' => 'simplenews_node_tab_page',
    'page arguments' => array(
      1,
    ),
    'weight' => 2,
  );
  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');
}

/**
 * Menu item access callback.
 *
 * Access for Node Tab
 */
function simplenews_node_tab_access($node = NULL) {
  $simplenews_types = variable_get('simplenews_content_types', array(
    'simplenews',
  ));
  if (in_array($node->type, $simplenews_types) && user_access('send newsletter')) {
    return TRUE;
  }
  else {
    return FALSE;
  }
}

/**
 * Menu callback. Displays newsletter-related stuff of a node.
 */
function simplenews_node_tab_page($node = NULL) {
  return drupal_get_form('simplenews_node_tab_send_form', $node);
}

/**
 * Simplenews tab form
 */
function simplenews_node_tab_send_form($form_state, $node) {

  // Prepare
  $simplenews_values = _simplenews_get_node_defaults();
  if (isset($node->simplenews) && !empty($node->simplenews)) {
    $simplenews_values = $node->simplenews;
  }

  // Flatten the values for e.g. preview to make them accessible same as default and load.
  $simplenews_values = _simplenews_flatten_array($simplenews_values);
  $form = array();

  // We will need the node
  $form['nid'] = array(
    '#type' => 'hidden',
    '#value' => $node->nid,
  );
  $form['simplenews'] = array(
    '#type' => 'fieldset',
    '#title' => t('Send newsletter'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
    // We need #tree to persist key 'simplenews' in submissions
    '#tree' => TRUE,
  );

  // Add tid
  $vid = variable_get('simplenews_vid', '');
  foreach ($node->taxonomy as $tid => $term) {
    if ($term->vid == $vid) {
      $form['simplenews']['tid'] = array(
        '#type' => 'hidden',
        '#value' => $term->tid,
      );
      break;
    }
  }

  // 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) {
    $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' => variable_get('simplenews_send', SIMPLENEWS_COMMAND_SEND_TEST),
      '#options' => $options,
      '#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),
      );
    }

    // include function that provides the priority values
    module_load_include('inc', 'simplenews', 'includes/simplenews.admin');
    $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' => simplenews_get_priority(),
    );
    $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',
      '#return_value' => 0,
      '#attributes' => array(
        'checked' => 'checked',
        'disabled' => 'disabled',
      ),
    );
    $form['simplenews']['none']['#title'] = $simplenews_values['s_status'] == SIMPLENEWS_STATUS_SEND_READY ? t('This newsletter has been sent.') : t('This newsletter is pending.');

    // We return now, because there's no point in having a submit button
    return $form;
  }
  $form['simplenews']['s_status'] = array(
    '#type' => 'hidden',
    '#value' => isset($simplenews_values['s_status']) ? $simplenews_values['s_status'] : SIMPLENEWS_STATUS_SEND_NOT,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  return $form;
}

/**
 * Simplenews tab form validate callback
 */
function simplenews_node_tab_send_form_validate($form, &$form_state) {
  $values = $form_state['values'];
  if (isset($values['simplenews']['send']) && $values['simplenews']['send'] == SIMPLENEWS_COMMAND_SEND_TEST) {
    if (!empty($values['simplenews']['test_address'])) {
      $mails = explode(',', $values['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 (!simplenews_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.'));
    }
  }
}

/**
 * Simplenews tab form submit callback
 */
function simplenews_node_tab_send_form_submit($form, &$form_state) {

  // Get the node
  $values = $form_state['values'];
  $node = node_load($values['nid']);
  $s_status = SIMPLENEWS_STATUS_SEND_NOT;
  if ($values['simplenews']['send'] == SIMPLENEWS_COMMAND_SEND_NOW) {
    $s_status = SIMPLENEWS_STATUS_SEND_PENDING;
  }
  $values['simplenews']['s_status'] = $s_status;

  // Is this a multilingual newsletter ?
  // 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) && $node->tnid != 0) {
    if ($translations = translation_node_get_translations($node->tnid)) {
      foreach ($translations as $translation) {

        // translation_node_get_translation() only returns nid, title and language
        // For consistency, we load the full node
        $translation = node_load($translation->nid);
        simplenews_newsletter_update($translation, $values['simplenews']);
      }
    }
    else {
      simplenews_newsletter_update($node, $values['simplenews']);
    }
  }
  else {
    simplenews_newsletter_update($node, $values['simplenews']);
  }

  // Update $node before send
  $node->simplenews = _simplenews_flatten_array($values['simplenews']);

  // Send newsletter or test newsletter
  module_load_include('inc', 'simplenews', 'includes/simplenews.mail');
  if ($values['simplenews']['send'] == SIMPLENEWS_COMMAND_SEND_NOW) {

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

    // Send test newsletter to test address(es)
    simplenews_send_test($node);
  }
}

/**
 * Update {simplenews_newsletter} table
 *
 * @param node $node the current node (or translation) that should be updated
 * @param array $param the values which to update the newsletter with
 */
function simplenews_newsletter_update($node, $param = array()) {

  // Is there already a corresponding newsletter ?
  $sn_nid = db_result(db_query("\n    SELECT nid\n    FROM {simplenews_newsletters}\n    WHERE nid = %d", $node->nid));
  if (!is_numeric($sn_nid)) {

    // No newsletter => we create it
    db_query("\n      INSERT INTO {simplenews_newsletters}\n      (nid, vid, tid, s_status, s_format, priority, receipt)\n      VALUES (%d, %d, %d, %d, '%s', %d, %d)", $node->nid, $node->vid, $param['tid'], $param['s_status'], $param['advanced']['s_format'], $param['advanced']['priority'], $param['advanced']['receipt']);
  }
  else {

    // Already existing => we update it
    db_query("\n      UPDATE {simplenews_newsletters}\n      SET vid = %d,\n      tid = %d,\n      s_status = %d,\n      s_format = '%s',\n      priority = %d,\n      receipt = %d\n      WHERE nid = %d", $node->vid, $param['tid'], $param['s_status'], $param['advanced']['s_format'], $param['advanced']['priority'], $param['advanced']['receipt'], $node->nid);
  }
}

/**
 * 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(
        'simplenews',
      ));
      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(
        'simplenews',
      ));

      // apply nodetype rename
      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)) {
        $tid = db_result(db_query("\n          SELECT s.tid\n          FROM {simplenews_newsletters} s\n          INNER JOIN {node} n\n            ON s.nid = n.nid\n          WHERE n.nid = %d", $node->nid));
        global $language, $user;
        $context = array(
          'node' => $node,
          // note the context represents the current user account for presentation.
          'account' => $user,
          'newsletter' => taxonomy_get_term($tid),
        );

        // Check for function_exists to avoid fatal errors in 6--1 to 6--2 upgrade paths.
        if (isset($node->body) && function_exists('token_replace')) {
          $node->body = token_replace($node->body, 'simplenews', $context);
        }
        if (isset($node->teaser) && function_exists('token_replace')) {
          $node->teaser = token_replace($node->teaser, 'simplenews', $context);
        }
      }
      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' => taxonomy_vocabulary_load($vid)->name,
          '@settings' => url('admin/settings/simplenews/general'),
        )));
      }
      break;
    case 'insert':
    case 'update':

      // Initialize $node->simplenews in case it does not exist
      // (which should be the default case)
      if (empty($node->simplenews)) {
        $node->simplenews = _simplenews_get_newsletter_settings($node);
      }
      if (empty($node->simplenews)) {
        $node->simplenews = _simplenews_get_node_defaults();
      }

      // Get Simplenews vid
      $simplenews_vid = variable_get('simplenews_vid', '');

      // Drupal changed its taxonomy format on the insert and update operations
      // in version 6.24.
      $first_value = reset($node->taxonomy);

      // Drupal <= 6.23: $node->taxonomy[vid] = tid
      if (is_int($first_value)) {
        $node->simplenews['tid'] = $node->taxonomy[$simplenews_vid];
      }
      elseif (is_object($first_value)) {
        foreach ($node->taxonomy as $tid => $term) {
          if ($term->vid == $simplenews_vid) {
            $node->simplenews['tid'] = $term->tid;
          }
        }
      }

      // Call the same update function as tab submit function
      simplenews_newsletter_update($node, $node->simplenews);
      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,
        )));
      }

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

      // @todo: {simplenews_newsletters} data might be missing if an uninstall happened at some time with simplenews nodes created. provide reasonable defaults.
      $newsletter = db_fetch_array(db_query('
        SELECT *
        FROM {simplenews_newsletters}
        WHERE nid = %d', $node->nid));
      return array(
        'simplenews' => $newsletter,
      );
      break;
  }
}

/**
 * This helper function retrieves simplenews settings from database
 * and formats in for simplenews friendly default structure.
 *
 * @param stdClass $node
 *   $node for which we are looking for simplenews settings
 * @return array
 *   Function returns simplenews settings or NULL value
 *
 */
function _simplenews_get_newsletter_settings(stdClass $node) {
  $simplenews = db_fetch_array(db_query('
      SELECT *
      FROM {simplenews_newsletters}
      WHERE nid = %d', $node->nid));
  if (is_array($simplenews)) {
    $advanced_settings = array(
      's_format',
      'priority',
      'receipt',
    );
    $simplenews['advanced'] = array();
    foreach ($advanced_settings as $adv_field) {
      $simplenews['advanced'][$adv_field] = $simplenews[$adv_field];
      unset($simplenews[$adv_field]);
    }
    return $simplenews;
  }
  else {
    return NULL;
  }
}

/**
 * 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 simplenews.
 */
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;
            }
          }
        }
        elseif (is_object($term)) {
          $selected_terms[] = $term->tid;
        }
        elseif ($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. See 'Replacement patterns' for available variables.)");
      }
      $form['simplenews_subscription']['subscription_mail']['token_help'] = array(
        '#title' => t('Replacement patterns'),
        '#type' => 'fieldset',
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
        '#weight' => -20,
        '#description' => t('These tokens can be used in all text fields and will be replaced on-screen and in the email. Note that simplenews-receiver tokens are not suitable for on-screen use.'),
      );
      $form['simplenews_subscription']['subscription_mail']['token_help']['help'] = array(
        '#value' => theme('token_help', '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');
      }
    }
  }
}

/**
 * Implementation of hook_cron().
 */
function simplenews_cron() {
  module_load_include('inc', 'simplenews', 'includes/simplenews.mail');
  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':
        db_query('
          DELETE FROM {simplenews_snid_tid}
          WHERE tid = %d', $term['tid']);
        db_query("\n          DELETE FROM {blocks}\n          WHERE module = '%s'\n            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 'register':
      $options = $default_value = $hidden = array();

      // Determine the newsletters to which a user can choose to subscribe.
      // Determine to which other newsletter a user is automatically subscribed.
      foreach (simplenews_get_newsletters(variable_get('simplenews_vid', ''), TRUE) as $newsletter) {
        $subscribe_new_account = variable_get('simplenews_new_account_' . $newsletter->tid, 'none');
        $opt_inout_method = variable_get('simplenews_opt_inout_' . $newsletter->tid, 'double');
        if (($subscribe_new_account == 'on' || $subscribe_new_account == 'off') && ($opt_inout_method == 'single' || $opt_inout_method == 'double')) {
          $options[$newsletter->tid] = check_plain($newsletter->name);
          $default_value[$newsletter->tid] = $subscribe_new_account == 'on' ? $newsletter->tid : 0;
        }
        else {
          if ($subscribe_new_account == 'silent' || $subscribe_new_account == 'on' && $opt_inout_method == 'hidden') {
            $hidden[] = $newsletter->tid;
          }
        }
      }
      $form = array();
      if (count($options)) {
        $form['simplenews'] = array(
          '#type' => 'fieldset',
          '#title' => t('Newsletters'),
          '#description' => t('Select the newsletter(s) to which you wish to subscribe.'),
          '#weight' => 5,
        );
        $form['simplenews']['newsletters'] = array(
          '#type' => 'checkboxes',
          '#options' => $options,
          '#default_value' => $default_value,
        );
      }
      if (count($hidden)) {
        $form['simplenews_hidden'] = array(
          '#type' => 'hidden',
          '#value' => implode(',', $hidden),
        );
      }
      return $form;
      break;
    case 'insert':

      //  suppress login of different groups (e.g. logintoboggan) where mail address is not authenticated yet. Opt in/out settings are ignored in non-anonymous cases.
      // The user registration form may contain (hidden) form element to
      // subscribe to newsletters. Data of these elements are stored in
      // the $account->data variable.
      // We subscribe the user according to the (hidden) form elements.
      // take over previously anonymous subscribed mail.
      $query = "\n        SELECT snid\n        FROM {simplenews_subscriptions}\n        WHERE mail = '%s'";
      if ($result = db_fetch_object(db_query($query, $edit['mail']))) {
        db_query("\n          UPDATE {simplenews_subscriptions}\n          SET uid = %d,\n            language = '%s'\n          WHERE snid = %d", $edit['uid'], $edit['language'], $result->snid);
      }

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

      // Process subscription check boxes.
      if (isset($edit['newsletters'])) {
        foreach (array_keys(array_filter($edit['newsletters'])) as $tid) {
          simplenews_subscribe_user($account->mail, $tid, FALSE, 'website');
          $newsletters = simplenews_get_newsletters(variable_get('simplenews_vid', ''), TRUE);
          drupal_set_message(t('You have been subscribed to %newsletter.', array(
            '%newsletter' => $newsletters[$tid]->name,
          )));
        }
      }

      // set inactive if not created by an administrator. this needs a cleaner API.
      if (!user_access('administer users')) {
        db_query("\n          UPDATE {simplenews_subscriptions}\n          SET activated = %d\n          WHERE uid = %d", 0, $account->uid);
      }
      break;
    case 'login':

      // is authenticated user: activate subscription
      db_query("\n        UPDATE {simplenews_subscriptions}\n        SET activated = %d\n        WHERE uid = %d", 1, $account->uid);
      break;
    case 'update':

      // always keep uid, mail, language in sync
      if ($category == 'account' && $edit['mail']) {
        $query = "\n          SELECT snid\n          FROM {simplenews_subscriptions}\n          WHERE uid = %d";
        if ($result = db_fetch_object(db_query($query, $account->uid))) {

          // user has snid. if user changes mail to previously subscribed anonymous mail, remove subscription.
          db_query("\n            DELETE FROM {simplenews_subscriptions}\n            WHERE mail = '%s'\n              AND uid = 0", $edit['mail']);

          // migrate current subscription snid to new mail, language.
          db_query("\n            UPDATE {simplenews_subscriptions}\n            SET mail = '%s',\n              language = '%s'\n            WHERE snid = %d", $edit['mail'], $edit['language'], $result->snid);
        }
        else {

          // no current snid. try taking over subscription via user mail match.
          $query = "\n            SELECT snid\n            FROM {simplenews_subscriptions}\n            WHERE mail = '%s'";
          if ($result = db_fetch_object(db_query($query, $edit['mail']))) {
            db_query("\n              UPDATE {simplenews_subscriptions}\n              SET uid = %d,\n                language = '%s'\n              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)) {

          // TODO: since we can't detect if a user is in mail verification process, we always enable
          //  user subscriptions if user is active. this also matches for unverified users.
          //  we should depend on a contrib module to detect this state and suppress activation.
          $activate = $edit['status'];
          db_query("\n            UPDATE {simplenews_subscriptions}\n            SET activated = %d\n            WHERE uid = %d", $activate, $account->uid);
        }
      }
      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("\n          SELECT s.snid\n          FROM {simplenews_subscriptions} s\n          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("\n          UPDATE {simplenews_subscriptions}\n          SET uid = 0\n          WHERE uid = %d", $account->uid);
      }
      break;
    case 'form':
      if ($category == 'newsletter') {

        // This is reached only if op=categories access callback passed.
        module_load_include('inc', 'simplenews', 'includes/simplenews.mail');
        module_load_include('inc', 'simplenews', 'includes/simplenews.subscription');
        $form_state = array();
        $form = simplenews_subscriptions_account_form($form_state, $account);
        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'),
        );

        // Collect newsletter to which the current user is subscribed.
        // 'hidden' newsletters are not listed.
        foreach (simplenews_get_newsletters(variable_get('simplenews_vid', '')) 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
              AND t.status = %d', $account->uid, $newsletter->tid, SIMPLENEWS_SUBSCRIPTION_STATUS_SUBSCRIBED))) {
            $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,
          );
        }

        // Avoid notice.
        if (count(element_children($account->content['simplenews'])) == 0) {
          $account->content['simplenews']['#children'] = '';
        }
      }
      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();

      // Special block for multi
      $blocks[0] = array(
        'info' => t('Newsletter: Multi Subscription'),
      );
      foreach (simplenews_get_newsletters(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

        // Only list a block if the newsletter is not hidden.
        $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':

      // Special block for multi
      if ($delta == 0) {
        $form['simplenews_block_multiple']['simplenews_block_m_multiple'] = array(
          '#type' => 'textfield',
          '#title' => t('Block message'),
          '#size' => 60,
          '#maxlength' => 128,
          // @todo: clean localization / i18n needed
          '#default_value' => variable_get('simplenews_block_m_multiple', t('Select the newsletter(s) to which you want to subscribe or unsubscribe.')),
        );
      }
      else {
        $form['simplenews_block_' . $delta]['simplenews_block_m_' . $delta] = array(
          '#type' => 'textfield',
          '#title' => t('Block message'),
          '#size' => 60,
          '#maxlength' => 128,
          // @todo: clean localization / i18n needed
          '#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':

      //Special block for multi
      if ($delta == 0) {
        variable_set('simplenews_block_m_multiple', $edit['simplenews_block_m_multiple']);
      }
      else {
        variable_set('simplenews_block_m_' . $delta, $edit['simplenews_block_m_' . $delta]);
        variable_set('simplenews_block_f_' . $delta, $edit['simplenews_block_f_' . $delta]);
        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':

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

          // render block only if permitted
          $block = array(
            'subject' => t('Newsletters'),
            'content' => theme('simplenews_multi_block'),
          );
          return $block;
        }
        return NULL;
      }
      else {
        global $language;
        $newsletters = simplenews_get_newsletters(variable_get('simplenews_vid', ''));

        // Only display a block if $delta is a valid newsletter term id.
        if (in_array($delta, array_keys($newsletters))) {
          module_load_include('inc', 'simplenews', 'includes/simplenews.subscription');

          // $delta is validated, the block can be displayed.
          $block = array(
            'subject' => check_plain($newsletters[$delta]->name),
            'content' => theme(array(
              'simplenews_block__' . $delta,
              'simplenews_block',
            ), $delta),
          );
          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($form_id, $args) {
  $forms = array();
  $match = preg_match('/simplenews_block_form_(\\d+)/', $form_id, $matches);
  if ($match) {
    $forms['simplenews_block_form_' . $matches[1]] = array(
      'callback' => 'simplenews_block_form',
      'callback arguments' => array(
        $matches[1],
      ),
    );
  }
  return $forms;
}

/**
 * Load a user or creates a dummy anonymous user.
 *
 * @return 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-mail.
    $account = new stdClass();
    $account->uid = 0;
    $account->mail = $mail;
  }
  return $account;
}

/**
 * Update subscriber data.
 *
 * @param $snid
 *   Subscribers ID.
 * @param $data
 *   Array of data to be updated
 *   'language'  Preferred newsletter language (default: '')
 */
function simplenews_subscriber_update($subscription, $data) {
  $data['snid'] = $subscription->snid;
  drupal_write_record('simplenews_subscriptions', $data, 'snid');
}

/**
 * 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
 *
 * This function does NOT update language information if account already exists.
 *
 * @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.
 * @param string $source
 *   Indication for source of subscription. Simplenews uses these sources:
 *    website: via any website form (with or without confirmation email)
 *    mass subscribe: mass admin UI
 *    mass unsubscribe: mass admin UI
 *    action: Drupal actions
 *
 * @return $subscription
 */
function simplenews_subscribe_user($mail, $tid, $confirm = TRUE, $source = 'unknown', $preferred_language = NULL) {
  global $language;

  // 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("\n      INSERT INTO {simplenews_subscriptions}\n      (mail, uid, language, activated, timestamp)\n      VALUES ('%s', %d, '%s', %d, %d)", $mail, $account->uid, $preferred_language, 1, time());
    $subscription = simplenews_get_subscription($account);
  }
  if ($confirm) {
    module_load_include('inc', 'simplenews', 'includes/simplenews.mail');

    // 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])) {

    // Add user to newsletter relationship if not already subscribed.
    // Check if user is (un)subscribed to this newsletter.
    // Resubscribe or add new subscription.
    if (isset($subscription->newsletter_subscription[$tid])) {
      db_query("\n        UPDATE {simplenews_snid_tid}\n        SET status = %d,\n          timestamp = %d,\n          source = '%s'\n        WHERE snid = %d\n          AND tid = %d", SIMPLENEWS_SUBSCRIPTION_STATUS_SUBSCRIBED, time(), $source, $subscription->snid, $tid);
    }
    else {
      db_query("\n        INSERT INTO {simplenews_snid_tid}\n        (snid, tid, status, timestamp, source)\n        VALUES (%d, %d, %d, %d, '%s')", $subscription->snid, $tid, SIMPLENEWS_SUBSCRIPTION_STATUS_SUBSCRIBED, time(), $source);
    }

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

/**
 * 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 confirm unsubscribe
 * 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
 * @param string $source
 *   Indication for the unsubscribe source. Simplenews uses these sources:
 *    website: via any website form (with or without confirmation email)
 *    mass subscribe: mass admin UI
 *    mass unsubscribe: mass admin UI
 *    action: Drupal actions
 */
function simplenews_unsubscribe_user($mail, $tid, $confirm = TRUE, $source = 'unknown') {
  $account = (object) array(
    'mail' => $mail,
  );
  $subscription = simplenews_get_subscription($account);

  // The unlikely case that a user is unsubscribed 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) {
    module_load_include('inc', 'simplenews', 'includes/simplenews.mail');

    // Send confirmation email to user to confirm unsubscribe
    // 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])) {

    // Unsubscribe the user from the newsletter.
    db_query("\n      UPDATE {simplenews_snid_tid}\n      SET status = %d,\n        timestamp = %d,\n        source = '%s'\n      WHERE snid = %d\n        AND tid = %d", SIMPLENEWS_SUBSCRIPTION_STATUS_UNSUBSCRIBED, time(), $source, $subscription->snid, $tid);

    // Execute simplenews unsubscribe trigger
    simplenews_call_actions('unsubscribe', $subscription);
  }
  module_invoke_all('simplenews_unsubscribe_user', $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
 */

//@TODO only return active subscriptions.
function simplenews_user_is_subscribed($mail, $tid, $reset = FALSE) {
  static $subscribed = array();
  if ($reset) {
    $subscribed = array();
  }
  if (!isset($subscribed[$mail][$tid])) {
    $subscribed[$mail][$tid] = db_result(db_query("\n      SELECT COUNT(*)\n      FROM {simplenews_subscriptions} s\n      INNER JOIN {simplenews_snid_tid} t\n        ON s.snid = t.snid\n      WHERE s.mail = '%s'\n        AND t.tid = %d\n        AND t.status = %d", $mail, $tid, SIMPLENEWS_SUBSCRIPTION_STATUS_SUBSCRIBED)) ? 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 tid's of active subscriptions
 *     newsletter_subscriptions : array of newsletter subscription objects
 *     uid  :  user id. 0 if account is anonymous user
 *     mail :  user email address. empty if email is unknown
 *     name :  always empty. Added for compatibility with user account object
 *     language : language object. User preferred or default language
 *   )
 */
function simplenews_get_subscription($account) {

  // Load subscription data based on available account information
  // 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("\n      SELECT s.*, u.name\n      FROM {simplenews_subscriptions} s\n      LEFT JOIN {users} u\n        ON u.uid = s.uid\n      WHERE s.snid = %d", $account->snid));
  }
  elseif (isset($account->mail)) {
    $subscription = db_fetch_object(db_query("\n      SELECT s.*, u.name\n      FROM {simplenews_subscriptions} s\n      LEFT JOIN {users} u\n        ON u.uid = s.uid\n      WHERE LOWER(s.mail) = LOWER('%s')", $account->mail));
  }
  elseif (isset($account->uid) && $account->uid > 0) {
    $subscription = db_fetch_object(db_query("\n      SELECT s.*, u.name\n      FROM {simplenews_subscriptions} s\n      LEFT JOIN {users} u\n        ON u.uid = s.uid\n      WHERE s.uid = %d", $account->uid));
  }
  if (!empty($subscription)) {
    $result = db_query("\n      SELECT tid, status, timestamp, source\n      FROM {simplenews_snid_tid} t\n      WHERE t.snid = %d", $subscription->snid);
    $subscription->tids = array();
    while ($newsletter_subscription = db_fetch_object($result)) {
      if ($newsletter_subscription->status == SIMPLENEWS_SUBSCRIPTION_STATUS_SUBSCRIBED) {
        $subscription->tids[$newsletter_subscription->tid] = $newsletter_subscription->tid;
      }
      $subscription->newsletter_subscription[$newsletter_subscription->tid] = $newsletter_subscription;
    }

    // simplenews_subscription matches account language if registered.
    $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();
    $subscription->newsletter_subscription = 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);
}

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

/**
 * 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()
 * @param $reset
 *   Reset the static cache, only used internally for tests.
 */
function simplenews_mail($key, &$message, $params, $reset = FALSE) {

  // Message header, body and mail headers are buffered to increase
  // performance 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 retrieved from the
  // static $messages variable.
  // $message buffer is node and language specific.
  static $messages = array();
  if ($reset) {
    $messages = array();
    return;
  }
  module_load_include('inc', 'simplenews', 'includes/simplenews.mail');
  $context = $params['context'];
  $langcode = $message['language']->language;
  switch ($key) {
    case 'node':
    case 'test':

      // 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;
      if (module_exists('translation')) {

        // If the node has translations and a translation is required
        // the equivalent of the node in the required language 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])) {
        $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
        $subject = '[[simplenews-newsletters-name]] [title-raw]';
        if ($tid = $node->simplenews['tid']) {
          $term = taxonomy_get_term($tid);

          // Translate the newsletter term name if simplenews vocabulary uses Localized terms.
          $name = _simplenews_tt_newsletter_name($term, $langcode);
          $subject = variable_get('simplenews_email_subject_' . $term->tid, $subject);
        }
        else {
          $name = t('Unassigned newsletter');
        }

        //$subject = theme('simplenews_newsletter_subject', $name, $node->title, $message['language']);
        $subject = token_replace($subject, 'simplenews', $context);
        $subject = token_replace($subject, 'node', $context['node']);
        $subject = str_replace(array(
          "\r",
          "\n",
        ), '', $subject);
        $message['subject'] = $messages[$nid][$langcode]['subject'] = $subject;

        // Set the active language to the node's language.
        // This needs to be done as otherwise the language used to send the mail
        // is the language of the user logged in.
        if (module_exists('i18n')) {
          i18n_selection_mode('node', $node->language);
        }

        // 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;
        if (variable_get('simplenews_opt_inout_' . $tid, 'double') != 'hidden') {

          // TODO: Unsubscribe links are broken if key=='test' && $context['snid']==0
          //  We might pass snid / context to theme functions.
          // Build and buffer message footer
          $footer = theme(array(
            'simplenews_newsletter_footer__' . $context['node']->simplenews['tid'],
            'simplenews_newsletter_footer',
          ), $context, $key, $message['language']);
          $messages[$nid][$langcode]['footer'] = $footer;
        }

        // Reset the language to the original settings.
        if (module_exists('i18n')) {
          i18n_selection_mode('reset');
        }
      }
      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.
      $body = token_replace($body, 'simplenews', $context);

      // Convert to plain text if required.
      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.
      $footer = token_replace($footer, 'simplenews', $context);
      $message['body']['footer'] = $footer;

      // Add user specific header data.
      $message['headers']['List-Unsubscribe'] = '<' . token_replace('[simplenews-unsubscribe-url]', 'simplenews', $context) . '>';
      break;
    case 'subscribe':

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

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

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

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

/**
 * Get array of newsletters with names translated.
 *
 * If required newsletter names are translated based on the global language.
 * Hidden newsletters (opt-in/out setting is 'hidden') are not included.
 * @param int $vid
 *   Newsletter vocabulary ID
 * @param boolean $show_all
 *   On false hidden newsletters will not be returned.
 *   On true all newsletter will be returned.
 * @return array of newsletters objects.
 */
function simplenews_get_newsletters($vid, $show_all = FALSE, $reset = FALSE) {
  static $newsletters = NULL;
  if (is_array($newsletters) && !$reset) {
    return $newsletters;
  }
  $newsletters = array();
  foreach (taxonomy_get_tree($vid) as $term) {
    if (variable_get('simplenews_opt_inout_' . $term->tid, 'double') != 'hidden' || $show_all) {
      $newsletter = new stdClass();
      $newsletter->tid = $term->tid;
      $newsletter->name = _simplenews_tt_newsletter_name($term);
      $newsletters[$term->tid] = $newsletter;
    }
  }
  return $newsletters;
}

/**
 * Implementation of hook_token_list().
 */
function simplenews_token_list($type = 'all') {
  $tokens = array();
  switch ($type) {
    case 'simplenews_subscription':
      $tokens['simplenews']['simplenews-subscribe-url'] = t('URL of the subscribe confirmation page');
      $tokens['simplenews']['simplenews-unsubscribe-url'] = t('URL of the unsubscribe confirmation page');
      $tokens['simplenews']['simplenews-receiver-mail'] = t('Email address of the newsletter receiver');
      $tokens['simplenews']['simplenews-from-name'] = t('From name');
      $tokens['simplenews']['simplenews-from-mail'] = t('From email address');
      $tokens['simplenews']['simplenews-newsletters-name'] = t('The name of the newsletter series');
      $tokens['simplenews']['simplenews-newsletters-url'] = t('URL of the taxonomy page listing the issues of this newsletter series');
      $tokens['simplenews']['simplenews-subscriptions-url'] = t('URL of the manage subscriptions page');
      break;
    case 'all':
    case 'simplenews':
      $tokens['simplenews']['simplenews-subscribe-url'] = t('URL of the subscribe confirmation page.');
      $tokens['simplenews']['simplenews-unsubscribe-url'] = t('URL of the unsubscribe confirmation page');
      $tokens['simplenews']['simplenews-receiver-name'] = t('Username of the newsletter receiver or anonymous user name.');
      $tokens['simplenews']['simplenews-receiver-mail'] = t('Email address of the newsletter receiver');
      $tokens['simplenews']['simplenews-from-name'] = t('From name');
      $tokens['simplenews']['simplenews-from-mail'] = t('Email address from which the newsletter is sent');
      $tokens['simplenews']['simplenews-newsletter-url'] = t('URL of this newsletter issue');
      $tokens['simplenews']['simplenews-newsletters-name'] = t('The name of the newsletter series');
      $tokens['simplenews']['simplenews-newsletters-url'] = t('URL of the taxonomy page listing the issues of this newsletter series');
      $tokens['simplenews']['simplenews-subscriptions-url'] = t('URL of the manage subscriptions page');
      break;
  }
  return $tokens;
}

/**
 * Implementation of hook_token_value().
 */
function simplenews_token_values($type, $object = NULL, $options = array()) {
  $values = array();
  switch ($type) {
    case 'all':
    case 'simplenews':
      $account = $object['account'];
      $node = $object['node'];
      $language = isset($account->language) ? $account->language : language_default();
      $langcode = $language->language;
      $urlargs = array(
        'absolute' => TRUE,
        'language' => $language,
      );

      // Build tokens for 'simplenews' only.
      $values['simplenews-receiver-name'] = !empty($account->name) ? check_plain($account->name) : check_plain(variable_get('anonymous', 'Anonymous'));
      $values['simplenews-newsletter-url'] = url('node/' . $node->nid, $urlargs);

      // Try to switch to translation if possible.
      if ($node->tnid) {
        $translations = translation_node_get_translations($node->tnid);
        $node_translated = $translations[$langcode];
        if ($node_translated) {
          $values['simplenews-newsletter-url'] = url('node/' . $node_translated->nid, $urlargs);
        }
      }

    // Intentionally fall through (no break).
    case 'simplenews_subscription':
      $account = $object['account'];
      $newsletter = $object['newsletter'];
      $node = isset($object['node']) ? $object['node'] : '';
      $language = isset($account->language) ? $account->language : language_default();
      $urlargs = array(
        'absolute' => TRUE,
        'language' => $language,
      );

      // Build hash for (un)subscribe URL.
      $hash = '';
      if (isset($account->snid) && isset($newsletter->tid)) {
        $hash = _simplenews_generate_hash($account->mail, $account->snid, $newsletter->tid);
      }

      // When simplenews_token_values() is called from simplenews_nodeapi()
      // $newsletter->name contains no value. Newsletter name is
      // reconstructed from $node->simplenews['tid'].
      if (!isset($newsletter->name) || !isset($newsletter->tid)) {
        $tid = isset($newsletter->tid) ? $newsletter->tid : $node->simplenews['tid'];
        if ($tid && ($term = taxonomy_get_term($tid))) {
          $newsletter->name = $term->name;
          $newsletter->tid = $tid;
        }
      }
      module_load_include('inc', 'simplenews', 'includes/simplenews.mail');

      // Translate newsletter name
      $newsletter_name = _simplenews_tt_newsletter_name($newsletter, $language->language);

      // Info for simplenews-from-name and simplenews-from-mail tokens
      $from = _simplenews_set_from($node);

      // Build tokens for both 'simplenews_subscription' and 'simplenews'.
      $values['simplenews-subscribe-url'] = url('newsletter/confirm/add/' . $hash, $urlargs);
      $values['simplenews-unsubscribe-url'] = url('newsletter/confirm/remove/' . $hash, $urlargs);
      $values['simplenews-receiver-mail'] = $account->mail;
      $values['simplenews-from-name'] = $from['formatted'];
      $values['simplenews-from-mail'] = $from['address'];
      $values['simplenews-newsletters-name'] = $newsletter_name ? check_plain($newsletter_name) : '';
      $values['simplenews-newsletters-url'] = '';
      if (isset($newsletter->tid)) {
        $values['simplenews-newsletters-url'] = url('taxonomy/term/' . $newsletter->tid, $urlargs);
      }
      $values['simplenews-subscriptions-url'] = url('newsletter/subscriptions', $urlargs);
      break;
  }
  return $values;
}

/**
 * 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 when they (un)subscribe. Users can (un)subscribe using a form and a block. A <strong>subscription block</strong> is available for each newsletter offering a subscription form, a link to recent newsletters and RSS feed. Email addresses can also be imported and exported via the subscription administration pages.') . "</p>\n";
      $help .= "<h2>" . t('Configuration') . "</h2>\n";
      $help .= "<ul>";
      if (user_access('administer permissions')) {
        $help .= "<li>" . l(t('Configure permissions'), 'admin/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 '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> or <a href="!html_mail_url">HTML Mail</a> to send HTML emails or emails with attachments (both plain text and HTML).', array(
          '!mime_mail_url' => 'http://drupal.org/project/mimemail',
          '!html_mail_url' => 'http://drupal.org/project/htmlmail',
        )) . "</li>\n";
      }
      $help .= "</ul>";
      return $help;
    case 'admin/settings/simplenews/subscription':
      if (variable_get('language_count', 1) > 1) {
        if (module_exists('i18nstrings')) {
          global $language;
          $language_default = variable_get('language_default', $language);
          $help = '<p>' . t('This is a Multilingual website. Enter text for confirmation subject and body in the default site language (@language).', array(
            '@language' => $language_default->name,
          )) . "</p>\n";
        }
        else {
          $help = '<p>' . t('This is a Multilingual website. <a href="@url">Enable the String translation module</a> to enable translation of the confirmation subject and body.', array(
            '@url' => url('admin/build/modules'),
          )) . "</p>\n";
        }
        return $help;
      }
      break;
    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;
  }

  // Cover node add simplenews content types.
  if ($arg[0] == 'node' && $arg[1] == 'add') {
    $type = str_replace('-', '_', $arg[2]);
    if (in_array($type, variable_get('simplenews_content_types', array(
      'simplenews',
    )))) {
      $help = '<ul>';
      $help .= "<li>" . t('Add this newsletter issue to a newsletter by selecting a newsletter from the select list. To send this newsletter issue, first save the node, then use the "Newsletter" tab.') . "</li>\n";
      if (user_access('administer simplenews settings')) {
        $help .= "<li>" . t('Set default send options at <a href="@configuration">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;
    }
  }
}

/**
 * Implementation of hook_locale().
 */
function simplenews_locale($op = 'groups', $group = NULL) {
  switch ($op) {
    case 'groups':
      return array(
        'simplenews' => t('Simplenews'),
      );
      break;
    case 'info':
      $info['simplenews']['refresh callback'] = 'simplenews_locale_refresh';
      return $info;
      break;
  }
}

/**
 * Refresh translatable strings.
 *
 * @see _simplenews_subscription_confirmation_text()
 */
function simplenews_locale_refresh() {
  $keys = array(
    'subscribe_unsubscribed',
    'subscribe_subscribed',
    'unsubscribe_subscribed',
    'unsubscribe_unsubscribed',
    'subscribe_subject',
  );
  foreach ($keys as $key) {
    i18nstrings_update('simplenews:' . $key, _simplenews_subscription_confirmation_text($key, NULL, FALSE));
  }
  return TRUE;
}

/**
 * Generate default and custom subscription confirmation email text.
 *
 * @param string $key
 *   Text identification key
 * @param object $langcode
 *   Language code
 * @param boolean $translate
 *   FALSE: force return value to be untranslated text.
 * @return
 *   Invitation text. Optionally translated.
 *
 * @see simplenews_locale()
 */
function _simplenews_subscription_confirmation_text($key, $langcode = NULL, $translate = TRUE) {
  $text = variable_get('simplenews_confirm_' . $key, FALSE);

  // If administrator did not change the text, the variable is empty.
  // We get the default here.
  if (!$text) {
    $args = array();
    switch ($key) {
      case 'subscribe_unsubscribed':
        $text = t("We have received a request to subscribe [simplenews-receiver-mail] to the [simplenews-newsletters-name] newsletter on [site-name] website at [site-url]. To confirm this subscription please use the link below.\n\n[simplenews-subscribe-url]", $args, $langcode);
        break;
      case 'subscribe_subscribed':
        $text = t("We have received a request to subscribe [simplenews-receiver-mail] to the [simplenews-newsletters-name] newsletter on [site-name] website at [site-url]. However, this email is already subscribed to this newsletter. If you intended to unsubscribe please visit our site at:\n[site-url]", $args, $langcode);
        break;
      case 'unsubscribe_subscribed':
        $text = t("We have received a request to unsubscribe [simplenews-receiver-mail] from the [simplenews-newsletters-name] on [site-name] website at [site-url]. To confirm this cancellation please use the link below.\n\n[simplenews-unsubscribe-url]", $args, $langcode);
        break;
      case 'unsubscribe_unsubscribed':
        $text = t("We have received a request to unsubscribe [simplenews-receiver-mail] from the [simplenews-newsletters-name] on [site-name] website at [site-url]. However, this email is not subscribed to this newsletter. If you intended to subscribe please visit our site at:\n[site-url]", $args, $langcode);
        break;
      case 'subscribe_subject':
        $text = t("Confirmation for [simplenews-newsletters-name] from [site-name]", $args, $langcode);
        break;
    }
  }

  // If this is a multilingual website we use i18nstrings module to translate the content.
  if (variable_get('language_count', 1) > 1 && function_exists('i18nstrings') && $translate) {
    $text = i18nstrings('simplenews:' . $key, $text, $langcode);
  }
  return $text;
}

/**
 * Helper function to translate a newsletter name if required.
 *
 * @param <object> $newsletter
 *   Newsletter object. Typically from taxonomy_get_term().
 *    $newsletter -> tid    newsletter tid
 *    $newsletter -> name   newsletter name
 * @param <string> $langcode
 *   Optional language code (defaults to current global $language);
 *
 * @return <string> translated newsletter name.
 */
function _simplenews_tt_newsletter_name($newsletter, $langcode = NULL) {
  global $language;
  $langcode = isset($langcode) ? $langcode : $language->language;
  if (module_exists('i18ntaxonomy') && i18ntaxonomy_vocabulary(variable_get('simplenews_vid', '')) == I18N_TAXONOMY_LOCALIZE) {
    return tt('taxonomy:term:' . $newsletter->tid . ':name', $newsletter->name, $langcode);
  }
  return $newsletter->name;
}

/**
 * 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.
 *
 * Mime Mail or HTML 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;
}

/**
 * Get defaults for the simplenews node form.
 */
function _simplenews_get_node_defaults() {
  $defaults = array(
    'advanced' => array(
      's_format' => variable_get('simplenews_format', 'plain'),
      'priority' => variable_get('simplenews_priority', SIMPLENEWS_PRIORITY_NONE),
      'receipt' => variable_get('simplenews_receipt', 0),
    ),
    's_status' => '0',
    'test_address' => variable_get('simplenews_test_address', variable_get('site_mail', ini_get('sendmail_from'))),
  );
  return $defaults;
}

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

/**
 * Simplenews email verification
 *
 * Wrapper for supporting community modules
 */
function simplenews_valid_email_address($email = '') {
  if (module_exists('email_verify')) {

    //Same validation as in email_verify.module on line 33
    return module_invoke('email_verify', 'check', $email);
  }
  return valid_email_address($email);
}

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

/**
 * Process variables to format the simplenews block.
 *
 * Collect data and apply access restrictions.
 *
 * $variables
 *
 * @see simplenews-block.tpl.php
 */
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
  // @todo: clean localization / i18n needed
  $variables['message'] = check_plain(variable_get('simplenews_block_m_' . $tid, t('Stay informed on our latest news!')));
  if (user_access('subscribe to newsletters')) {
    module_load_include('inc', 'simplenews', 'includes/simplenews.subscription');
    $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);
}

/**
 * Process variables to format the simplenews newsletter footer.
 *
 * $variables are empty:
 *
 * @see simplenews-multi-block.tpl.php
 */
function template_preprocess_simplenews_multi_block(&$variables) {
  global $user;

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

  // Additional variables
  $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: newsletter node object
 * - $language: language object
 * - $key: email key [node|test]
 *
 * @see simplenews-newsletter-footer.tpl.php
 */
function template_preprocess_simplenews_newsletter_footer(&$variables) {
  $variables['format'] = $variables['context']['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);
}

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

Functions

Namesort descending Description
simplenews_block Implementation of hook_block().
simplenews_call_actions Call simplenews actions.
simplenews_content_build_modes Implementation of hook_content_build_modes().
simplenews_cron Implementation of hook_cron().
simplenews_cronapi Implements hook_cronapi().
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_newsletters Get array of newsletters with names translated.
simplenews_get_subscription Get the subscription object for the given account.
simplenews_help Implementation of hook_help().
simplenews_init Implementation of hook_init().
simplenews_locale Implementation of hook_locale().
simplenews_locale_refresh Refresh translatable strings.
simplenews_mail Implementation of hook_mail().
simplenews_menu Implementation of hook_menu().
simplenews_newsletter_access Menu item access callback.
simplenews_newsletter_update Update {simplenews_newsletter} table
simplenews_nodeapi Implementation of hook_nodeapi().
simplenews_node_tab_access Menu item access callback.
simplenews_node_tab_page Menu callback. Displays newsletter-related stuff of a node.
simplenews_node_tab_send_form Simplenews tab form
simplenews_node_tab_send_form_submit Simplenews tab form submit callback
simplenews_node_tab_send_form_validate Simplenews tab form validate callback
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_subscriber_update Update subscriber data.
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_token_list Implementation of hook_token_list().
simplenews_token_values Implementation of hook_token_value().
simplenews_unsubscribe_user Unsubscribe a user from a newsletter or send a confirmation mail.
simplenews_user Implementation of hook_user().
simplenews_user_is_subscribed
simplenews_validate_taxonomy Validate if selected terms are Newsletter taxonomy terms.
simplenews_valid_email_address Simplenews email verification
simplenews_views_api Implementation of hook_views_api().
template_preprocess_simplenews_block Process variables to format the simplenews block.
template_preprocess_simplenews_multi_block Process variables to format the simplenews newsletter footer.
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_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_get_newsletter_settings This helper function retrieves simplenews settings from database and formats in for simplenews friendly default structure.
_simplenews_get_node_defaults Get defaults for the simplenews node form.
_simplenews_subscription_confirmation_text Generate default and custom subscription confirmation email text.
_simplenews_tt_newsletter_name Helper function to translate a newsletter name if required.
_simplenews_user_load Load a user or creates a dummy anonymous user.

Constants