$message['headers']['From'] = $params['from']['formatted'];
if (isset($context['account']->snid) && isset($params['newsletter']->tid)) {
$hash = _simplenews_generate_hash($context['account']->mail, $context['account']->snid, $params['newsletter']->tid);
}
else {
$hash = NULL;
}
$variables = user_mail_tokens($context['account'], $context['account']->language);
$variables += array('!confirmation_url' => url('newsletter/confirm/remove/'. $hash, array('absolute' => TRUE)));
$variables += array('!newsletter' => $params['newsletter']->name);
$message['subject'] = _simplenews_subscription_confirmation_text('subscribe_subject', $context['account']->language, $variables);
if (simplenews_user_is_subscribed($context['account']->mail, $params['newsletter']->tid)) {
$message['body'] = _simplenews_subscription_confirmation_text('unsubscribe_subscribed', $context['account']->language, $variables);
}
else {
$message['body'] = _simplenews_subscription_confirmation_text('unsubscribe_unsubscribed', $context['account']->language, $variables);
}
break;
}
// Debug message to check for outgoing emails messages.
// Newsletters ($key = 'node') are not send now. Debugging will take place in simplenews_mail_send() when the newsletters are send
if (variable_get('simplenews_debug', FALSE) && $key != 'node') {
watchdog('simplenews', 'Outgoing email. Message type: %type
Subject: %subject
Recipient: %to', array('%type' => $key, '%to' => $message['to'], '%subject' => $message['subject']), WATCHDOG_DEBUG);
}
}
/**
* Send simplenews newsletters from the spool.
*
* Iindividual newsletter emails are stored in database spool.
* Sending is triggered by cron or immediately when the node is saved.
* Mail data is retreived from the spool, rendered and send one by one
* If sending is succesful the message is marked as send in the spool.
*/
function simplenews_mail_send($nid = NULL, $vid = NULL, $limit = NULL) {
// Send pending messages from database cache
// A limited number of mails is retrieved from the spool
$limit = isset($limit) ? $limit : variable_get('simplenews_throttle', 20);
if ($messages = simplenews_mail_spool_get(SIMPLENEWS_SPOOL_PENDING, $nid, $vid, $limit)) {
$mail_sent = array();
foreach ($messages as $key => $message) {
// Get subscription data for recipient and language
$account = new stdClass();
$account->mail = $message['mail'];
$subscription = simplenews_get_subscription($account);
$params['context']['account'] = $subscription;
// Get node data for the mail
$node = node_load(array('nid' => $message['nid'], 'vid' => $message['vid']));
$params['from'] = _simplenews_set_from($node);
$params['context']['node'] = $node;
// Send mail
$message = drupal_mail('simplenews', 'node', $subscription->mail, $subscription->language, $params, $params['from']['address'], TRUE);
if (variable_get('simplenews_debug', FALSE)) {
watchdog('simplenews', 'Outgoing email. Message type: %type
Subject: %subject
Recipient: %to', array('%type' => 'node', '%to' => $message['to'], '%subject' => $message['subject']), WATCHDOG_DEBUG);
}
// Succesfull mail messages are marked in the spool with status send.
// Succesfull mail are recorded with their spool id.
if ($message['result']) {
$mail_sent[] = $key;
}
}
// Mark messages in the spool as sent and update newsletter status.
if (!empty($mail_sent)) {
simplenews_mail_spool_update($mail_sent, SIMPLENEWS_SPOOL_SEND);
}
simplenews_send_status_update();
}
}
/**
* Store mail message in mail cache table.
*
* @param array $message data array to be stored
* $message['mail']
* $message['nid']
* $message['vid']
* $message['tid']
* $message['status'] (Default: 1 = pending)
* $message['time'] (default: current unix timestamp)
* @param array $message Mail message array
*/
function simplenews_mail_spool_set($message) {
$status = isset($message['status']) ? $message['status'] : SIMPLENEWS_SPOOL_PENDING;
$time = isset($message['time']) ? $message['time'] : time();
db_query("INSERT INTO {simplenews_mail_spool} (mail, nid, vid, tid, status, timestamp)
VALUES ('%s', %d, %d, %d, %d, %d)", $message['mail'], $message['nid'], $message['vid'], $message['tid'], $status, $time);
}
/**
* Retreive data from mail spool
*
* @param string $status Status of data to be retreived (0 = hold, 1 = pending, 2 = send)
* @param integer $nid node id
* @param integer $vid node version id
* @param integer $limit The maximum number of mails to load from the spool
*
* @return array Mail message array
* $message['msid']
* $message['mail']
* $message['nid']
* $message['vid']
* $message['tid']
* $message['status']
* $message['time']
*/
function simplenews_mail_spool_get($status, $nid = NULL, $vid = NULL, $limit = 999999) {
$result = db_query_range("SELECT * FROM {simplenews_mail_spool} s WHERE s.status = %d ORDER BY s.timestamp ASC", $status, 0, $limit);
while ($data = db_fetch_array($result)) {
$message = array();
foreach ($data as $key => $value) {
$message[$key] = $value;
}
$messages[$data['msid']] = $message;
}
return $messages;
}
/**
* Update status of mail data in spool table.
*
* Time stamp is set to current time.
*
* @param array $msids Mail spool id of record to be updated
* @param string $status New status (0 = hold, 1 = pending, 2 = send)
*/
function simplenews_mail_spool_update($msids, $status) {
db_query("UPDATE {simplenews_mail_spool} SET status = %d, timestamp = %d WHERE msid IN (%s)", $status, time(), implode(',', $msids));
}
/**
* Count data in mail spool table.
*
* @param integer $nid newsletter node id
* @param integer $vid newsletter revision id
* @param string $status email send status
*
* @return array Mail message array
*/
function simplenews_mail_spool_count($nid, $vid, $status = SIMPLENEWS_SPOOL_PENDING) {
return db_result(db_query("SELECT COUNT(*) FROM {simplenews_mail_spool} WHERE nid = %d AND vid = %d AND status = %d", $nid, $vid, $status));
}
/**
* Remove records from mail spool table.
*
* All records with status 'send' and time stamp before the expiration date
* are removed from the spool.
*/
function simplenews_clear_spool() {
$expiration_time = time() - variable_get('simplenews_spool_expire', 0) * 86400;
db_query("DELETE FROM {simplenews_mail_spool} WHERE status = %d AND timestamp <= %d", SIMPLENEWS_SPOOL_SEND, $expiration_time);
}
/**
* Update newsletter send status.
*
* Set newsletter send status based on email send status in spool table.
* Translated and untranslated nodes get a different treatment.
*
* The spool table holds data for emails to be send and (optionally)
* already send emails. The simplenews_newsletters table contains the overall
* send status of each newsletter issue (node).
* Newsletter issues get the status pending when sending is innitiated. As
* long as unsend emails exist in the spool, the status of the newsletter remains
* unsend. When no pending emails are found the newsletter status is set 'send'.
*
* Translated newsletters are a group of nodes that share the same tnid ({node}.tnid).
* Only one node of the group is found in the spool, but all nodes should share
* the same state. Therefore they are checked for the combined number of emails
* in the spool.
*/
function simplenews_send_status_update() {
$counts = array(); // number pending of emails in the spool
$sum = array(); // sum of emails in the spool per tnid (translation id)
$send = array(); // nodes with the status 'send'
// For each pending newsletter count the number of pending emails in the spool.
$result = db_query("SELECT s.nid, s.vid, s.tid, n.tnid FROM {simplenews_newsletters} s JOIN {node} n ON s.nid = n.nid AND s.vid = n.vid WHERE s.s_status = %d", SIMPLENEWS_STATUS_SEND_PENDING);
while ($newsletter = db_fetch_object($result)) {
// nid-vid are combined in one unique key.
$counts[$newsletter->tnid][$newsletter->nid .'-'. $newsletter->vid] = simplenews_mail_spool_count($newsletter->nid, $newsletter->vid);
}
// Determine which nodes are send per translation group and per individual node.
foreach ($counts as $tnid => $node_count) {
// The sum of emails per tnid is the combined status result for the group of translated nodes.
// Untranslated nodes have tnid == 0 which will be ignored later.
$sum[$tnid] = array_sum($node_count);
foreach ($node_count as $nidvid => $count) {
// Translated nodes (tnid != 0)
if ($tnid != '0' && $sum[$tnid] == '0') {
$send[] = $nidvid;
}
// Untranslated nodes (tnid == 0)
elseif ($tnid == '0' && $count == '0') {
$send[] = $nidvid;
}
}
}
// Update overall newsletter status
if (!empty($send)) {
foreach ($send as $nidvid) {
// Split the combined key 'nid-vid'
$nid = strtok($nidvid, '-');
$vid = strtok('-');
db_query("UPDATE {simplenews_newsletters} SET s_status = '%s' WHERE nid = %d AND vid = %d", SIMPLENEWS_STATUS_SEND_READY, $nid, $vid);
}
}
}
/**
* Implementations of hook_views_api().
*/
function simplenews_views_api() {
return array(
'api' => 2,
);
}
/**
* Call simplenews actions.
*/
function simplenews_call_actions($op, $subscription) {
// Only call actions when the simplenews_action module is enabled.
if (!module_exists('simplenews_action')) {
return;
}
$aids = _trigger_get_hook_aids('simplenews', $op);
$context = array(
'hook' => 'simplenews',
'op' => $op,
'account' => $subscription,
);
foreach ($aids as $aid => $action_info) {
actions_do($aid, $subscription, $context);
}
}
/**
* Build formatted from-name and email for a mail object.
*
* Each newsletter (serie; tid) can have a different from address.
* The from name and address depend on the newsletter term tid which is included in the $node object
*
* @param object $node Node object of a simplenews newsletter
*
* @return array [address] = from address; [formatted] = formatted from name and address
*/
function _simplenews_set_from($node = NULL) {
$address_default = variable_get('site_mail', ini_get('sendmail_from'));
$name_default = variable_get('site_name', 'Drupal');
if (isset($node->simplenews['tid'])) {
$address = variable_get('simplenews_from_address_'. $node->simplenews['tid'], variable_get('simplenews_from_address', $address_default));
$name = variable_get('simplenews_from_name_'. $node->simplenews['tid'], variable_get('simplenews_from_name', $name_default));
}
else {
$address = variable_get('simplenews_from_address', $address_default);
$name = variable_get('simplenews_from_name', $name_default);
}
return array(
'address' => $address,
'formatted' => '"'. mime_header_encode(addslashes(check_plain($name))) .'" <'. $address .'>',
);
}
/**
* Build header array with priority and receipt confirmation settings.
*
* @param $node: node object
* @param $from: from email address
*
* @return Header array with priority and receipt confirmation info
*/
function _simplenews_headers($node, $from) {
$headers = array();
// If receipt is requested, add headers.
if ($node->simplenews['receipt']) {
$headers['Disposition-Notification-To'] = $from;
$headers['X-Confirm-Reading-To'] = $from;
}
// Add priority if set.
switch ($node->simplenews['priority']) {
case SIMPLENEWS_PRIORITY_HIGHEST:
$headers['Priority'] = 'High';
$headers['X-Priority'] = '1';
$headers['X-MSMail-Priority'] = 'Highest';
break;
case SIMPLENEWS_PRIORITY_HIGH:
$headers['Priority'] = 'urgent';
$headers['X-Priority'] = '2';
$headers['X-MSMail-Priority'] = 'High';
break;
case SIMPLENEWS_PRIORITY_NORMAL:
$headers['Priority'] = 'normal';
$headers['X-Priority'] = '3';
$headers['X-MSMail-Priority'] = 'Normal';
break;
case SIMPLENEWS_PRIORITY_LOW:
$headers['Priority'] = 'non-urgent';
$headers['X-Priority'] = '4';
$headers['X-MSMail-Priority'] = 'Low';
break;
case SIMPLENEWS_PRIORITY_LOWEST:
$headers['Priority'] = 'non-urgent';
$headers['X-Priority'] = '5';
$headers['X-MSMail-Priority'] = 'Lowest';
break;
}
// Add general headers
$headers['Precedence'] = 'bulk';
return $headers;
}
/**
* HTML to text conversion for HTML and special characters.
*
* Converts some special HTMLcharacters in addition to drupal_html_to_text()
*
* @param string $text Source text with HTML and special characters
*
* @return string Target text with HTML and special characters replaced
*/
//TODO: convert url to absolute url's with (right prefix ?)
function simplenews_html_to_text($text) {
// Remove in-page links
$pattern = '@]+?href="#[^"]*"[^>]*?>(.+?)@i';
$text = preg_replace($pattern, '$1', $text);
// Replace some special characters before performing the drupal standard conversion
$preg = _simplenews_html_replace();
$text = preg_replace(array_keys($preg), array_values($preg), $text);
// Perform standard drupal html to text conversion
return drupal_html_to_text($text);
}
/**
* List of preg* regular expression patterns to search for and replace with
*/
function _simplenews_html_replace() {
return array(
'/"/i' => '"',
'/>/i' => '>',
'/</i' => '<',
'/&/i' => '&',
'/©/i' => '(c)',
'/™/i' => '(tm)',
'/“/' => '"',
'/”/' => '"',
'/–/' => '-',
'/’/' => "'",
'/&/' => '&',
'/©/' => '(c)',
'/™/' => '(tm)',
'//' => '--',
'//' => '"',
'//' => '"',
'//' => '*',
'/®/i' => '(R)',
'/•/i' => '*',
'/€/i' => 'Euro ',
);
}
/**
* Replace tokens in node body/teaser with user specific variables.
*/
function simplenews_replace_vars($node, $teaser = TRUE) {
global $user;
$variables = user_mail_tokens($user, $user->language);
$node->body = strtr($node->body, $variables);
if ($teaser) {
$node->teaser = strtr($node->teaser, $variables);
}
return $node;
}
/**
* 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 = "". t('Simplenews publishes and sends newsletters to lists of subscribers. Both anonymous and authenticated users can opt-in to different mailing lists.') ."
\n";
$help .= "". t('Simplenews uses nodes for newsletter issues. Each newsletter issue is assigned to one newsletter by a taxonomy term. 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 spread the load of the mailserver.') ."
\n";
$help .= "". 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.') ."
\n";
$help .= "". t('Both anonymous and authenticated users can opt-in and opt-out to a newsletter. A confirmation message is sent to anonymous users at subscription and unsubscription. Users can (un)subscribe using a form and a block. A subscription block 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.') ."
\n";
$help .= "". t('Configuration') ."\n";
$help .= "";
if (user_access('administer permissions')) {
$help .= "- ". l(t('Configure permissions'), 'admin/user/permissions', array('fragment' => 'module-simplenews')) ."
\n";
}
if (user_access('administer simplenews settings')) {
$help .= "- ". l(t('Configure Simplenews'), 'admin/settings/simplenews') ."
\n";
}
if (user_access('administer blocks')) {
$help .= "- ". t('Enable a newsletter subscription block.', array('@admin_blocks' => url('admin/build/block'))) ."
\n";
}
if (user_access('administer simplenews settings')) {
$help .= "- ". t('Manage your newsletters, sent newsletters and subscriptions.', array('@newsletters' => url('admin/content/simplenews/types'), '@sent' => url('admin/content/simplenews'), '@subscriptions' => url('admin/content/simplenews/users'))) ."
\n";
}
$help .= "
";
$help .= "
". t('For more information, see the online handbook entry for Simplenews.', array('@handbook', 'http://drupal.org/node/197057')) ."
\n";
return $help;
case 'node/add/simplenews':
$help = '';
$help .= "- ". t('Add this newsletter issue to a newsletter by selecting a newsletter from the select list. Send a newsletter or a test newsletter by selecting the appropriate radio button and submitting the node.') ."
\n";
if (user_access('administer simplenews settings')) {
$help .= "- ". t('Set default send options at Administer > Site configuration > Simplenews > Newsletter.', array('@configuration' => url('admin/settings/simplenews/newsletter'))) ."
\n";
}
if (user_access('administer newsletters')) {
$help .= "- ". t('Set newsletter specific options at Administer > Content management > Newsletters > Newsletters.', array('@configuration' => url('admin/content/simplenews/types'))) ."
\n";
}
$help .= "
";
return $help;
case 'admin/settings/simplenews/newsletter':
$help = '';
$help .= '- '. t('These settings are default to all newsletters. Newsletter specific settings can be found at the newsletter\'s settings page.', array('@page' => url('admin/content/simplenews/types'))) ."
\n";
if (!module_exists('mimemail')) {
$help .= "- ". t('Install Mime Mail to send HTML emails or emails with attachments (both plain text and HTML).', array('@mime_mail_url' => 'http://drupal.org/project/mimemail')) ."
\n";
}
$help .= "
";
return $help;
case 'admin/content/simplenews/types/add':
$help = ''. t('You can create different newsletters (or subjects) to categorize your news (e.g. Cats news, Dogs news, ...).') ."
\n";
return $help;
}
}
/**
* Generate the hash key used for subscribe/unsubscribe link.
*/
function _simplenews_generate_hash($mail, $snid, $tid) {
return drupal_substr(md5($mail . simplenews_private_key()), 0, 10) . $snid .'t'. $tid;
}
/**
* Determine possible mail format options.
*
* The mime_mail module must be installed to send HTML mails.
*/
function _simplenews_format_options() {
$options = array('plain' => t('plain'));
if (module_exists('mimemail')) {
$options['html'] = t('html');
}
return $options;
}
/**
* Generate default and custom subscription confirmation email text.
*
* @param string $key text identification key
* @param object $language language object
* @param array $variables array of variables as used by t()
*/
function _simplenews_subscription_confirmation_text($key, $language = NULL, $variables = array()) {
$langcode = isset($language) ? $language->language : NULL;
if ($admin_setting = variable_get('simplenews_confirm_'. $key, FALSE)) {
// An admin setting overrides the default string.
return t($admin_setting, $variables, $langcode);
}
else {
// No override, return default string.
switch ($key) {
case 'subscribe_unsubscribed':
return t("We have received a request for subscription of !mailto to the !newsletter on !site website at !uri. To confirm this subscription please use the link below.\n\n!confirmation_url", $variables, $langcode);
case 'subscribe_subscribed':
return t("We have received a request for subscription of !mailto to the !newsletter on !site website at !uri. However, this email is already subscribed to this newsletter. If you intended to unsubscribe please visit our site: !uri", $variables, $langcode);
case 'unsubscribe_subscribed':
return t("We have received a request to unsubscribe !mailto from the !newsletter on !site website at !uri. To confirm this unsubscription please use the link below.\n\n!confirmation_url", $variables, $langcode);
case 'unsubscribe_unsubscribed':
return t("We have received a request to unsubscribe !mailto from the !newsletter on !site website at !uri. However, this email is not subscribed to this newsletter. If you intended to subscribe please visit our site: !uri", $variables, $langcode);
case 'subscribe_subject':
return t("Confirmation for !newsletter from !site", $variables, $langcode);
}
}
}
/**
* Implementation of hook_theme.
*/
function simplenews_theme() {
return array(
'simplenews_block' => array(
'template' => 'simplenews-block',
'arguments' => array('tid' => NULL),
),
'simplenews_status' => array(
'template' => 'simplenews-status',
'file' => 'simplenews.admin.inc',
'arguments' => array('status' => NULL, 'source' => NULL),
),
'simplenews_newsletter' => array(
'arguments' => array('node' => NULL, 'tid' => NULL),
),
'simplenews_newsletter_subject' => array(
'arguments' => array('name' => NULL, 'title' => NULL, 'language' => NULL),
),
'simplenews_newsletter_body' => array(
'arguments' => array('body' => NULL, 'title' => NULL, 'language' => NULL),
),
'simplenews_newsletter_footer' => array(
'arguments' => array('format' => NULL, 'hash' => NULL, 'test' => NULL, 'language' => NULL),
),
'simplenews_subscription_list' => array(
'file' => 'simplenews.admin.inc',
'arguments' => array('form' => NULL),
),
);
}
/**
* Process variables to format the simplenews block.
*
* Collect data and apply access restrictions.
*
* $variables contains:
* - $tid
*
* @see simplenews-block.tpl.php
* @see theme_simplenews-block()
*/
function template_preprocess_simplenews_block(&$variables) {
global $user;
$tid = $variables['tid'];
// Set default values in case of missing permission.
$variables['form'] = '';
$variables['subscription_link'] = '';
$variables['newsletter_link'] = '';
$variables['issue_list'] = '';
$variables['rssfeed'] = '';
// Block content variables
$variables['message'] = check_plain(variable_get('simplenews_block_m_'. $tid, t('Stay informed on our latest news!')));
if (user_access('subscribe to newsletters')) {
$variables['form'] = drupal_get_form('simplenews_block_form_'. $tid);
$variables['subscription_link'] = l(t('Manage my subscriptions'), 'newsletter/subscriptions');
}
if (user_access('view links in block') || user_access('administer newsletters')) {
$variables['newsletter_link'] = l(t('Previous issues'), 'taxonomy/term/'. $tid);
$recent = simplenews_recent_newsletters($tid, variable_get('simplenews_block_i_'. $tid, 5));
$variables['issue_list'] = theme('item_list', $recent, t('Previous issues'), 'ul');
$term = taxonomy_get_term($tid);
$variables['rssfeed'] = theme('feed_icon', url('taxonomy/term/'. $tid .'/0/feed'), t('@newsletter feed', array('@newsletter' => $term->name)));
}
// Block content control variables
$variables['use_form'] = variable_get('simplenews_block_f_'. $tid, 1);
$variables['use_issue_link'] = variable_get('simplenews_block_l_'. $tid, 1);
$variables['use_issue_list'] = variable_get('simplenews_block_i_status_'. $tid, 0);
$variables['use_rss'] = variable_get('simplenews_block_r_'. $tid, 1);
// Additional variables
$variables['subscribed'] = empty($user->uid) ? FALSE : (simplenews_user_is_subscribed($user->mail, $tid) == TRUE);
$variables['user'] = !empty($user->uid);
}
/**
* Theme the newsletter message subject and body.
*/
function theme_simplenews_newsletter($node, $tid) {
$term = taxonomy_get_term($tid);
$name = $term->name ? $term->name : t('Unassigned newsletter');
$node->subject = '['. $name .'] '. $node->title;
$node->body = ''. $node->title ."
\n". $node->body;
return $node;
}
/**
* Theme the newsletter email subject.
*/
function theme_simplenews_newsletter_subject($name, $title, $language) {
return '['. $name .'] '. $title;
}
/**
* Theme the newsletter message body.
*/
function theme_simplenews_newsletter_body($body, $title, $language) {
$output = ''. $title ."
\n";
$output .= $body;
return $output;
}
/**
* Theme the newsletter message footer.
*/
function theme_simplenews_newsletter_footer($format, $hash, $test = FALSE, $language) {
if ($format == 'html') {
$output = '';
}
else {
$output = "\n\n-- \n". t('Unsubscribe from this newsletter: !url', array('!url' => url('newsletter/confirm/remove/'. $hash, array('absolute' => TRUE, 'language' => $language))), $language->language);
}
if ($test) {
$output .= "\n- - - ". t('This is a test version of the newsletter. The above unsubscribe link does not work!', array(), $language->language) .' - - -';
}
return $output;
}