array( 'name' => t('Newsletter issue'), 'module' => 'simplenews', 'description' => t('A newsletter issue to be sent to subscribed e-mail addresses.'), ) ); } /** * Implementation of hook_perm(). */ function simplenews_perm() { return array('view links in block', 'create newsletter', 'edit own newsletter', 'edit any newsletter', 'delete own newsletter', 'delete any newsletter', 'administer newsletters', 'administer simplenews settings', 'send newsletter', 'subscribe to newsletters'); } /** * Implementation of hook_access(). */ function simplenews_access($op, $node) { global $user; if ($op == 'create') { if (user_access('create newsletter')) { return TRUE; } } if ($op == 'update') { if (user_access('edit any newsletters')) { return TRUE; } elseif (user_access('edit own newsletter') && $user->uid == $node->uid) { return TRUE; } } if ($op == 'delete') { if (user_access('delete any newsletters')) { return TRUE; } elseif (user_access('delete own newsletter') && $user->uid == $node->uid) { return TRUE; } } } /** * Implementation of hook_init(). */ function simplenews_init() { drupal_add_css(drupal_get_path('module', 'simplenews') .'/simplenews.css', 'module', 'all', TRUE); if (module_exists('views')) { include_once(drupal_get_path('module', 'simplenews') .'/simplenews_views.inc'); } } /** * Implementation of hook_menu(). */ function simplenews_menu() { $items['admin/content/simplenews'] = array( 'title' => 'Newsletters', 'description' => 'Manage newsletters and subscriptions.', 'type' => MENU_NORMAL_ITEM, 'page callback' => 'drupal_get_form', 'page arguments' => array('simplenews_admin_news'), 'access arguments' => array('administer newsletters'), 'file' => 'simplenews.admin.inc', ); $items['admin/content/simplenews/sent'] = array( 'title' => 'Sent issues', 'type' => MENU_DEFAULT_LOCAL_TASK, 'page arguments' => array('simplenews_admin_news'), 'access arguments' => array('administer newsletters'), 'file' => 'simplenews.admin.inc', 'weight' => -10, ); $items['admin/content/simplenews/notsent'] = array( 'title' => 'Drafts', 'type' => MENU_LOCAL_TASK, 'page arguments' => array('simplenews_admin_news', 'notsent'), 'access arguments' => array('administer newsletters'), 'weight' => -9, ); $items['admin/content/simplenews/types'] = array( 'title' => 'Newsletters', 'type' => MENU_LOCAL_TASK, 'page callback' => 'simplenews_types_overview', 'access arguments' => array('administer newsletters'), 'file' => 'simplenews.admin.inc', 'weight' => -8, ); $items['admin/content/simplenews/types/edit/%'] = array( 'title' => 'Newsletters', 'type' => MENU_CALLBACK, 'page callback' => 'drupal_get_form', 'page arguments' => array('simplenews_admin_types_form', 5), 'access arguments' => array('administer newsletters'), 'file' => 'simplenews.admin.inc', ); $items['admin/content/simplenews/types/delete/%'] = array( 'title' => 'Newsletters', 'type' => MENU_CALLBACK, 'page callback' => 'drupal_get_form', 'page arguments' => array('simplenews_admin_types_delete', 5), 'access arguments' => array('administer newsletters'), 'file' => 'simplenews.admin.inc', ); $items['admin/content/simplenews/types/list'] = array( 'title' => 'List newsletters', 'type' => MENU_DEFAULT_LOCAL_TASK, 'file' => 'simplenews.admin.inc', 'weight' => -10, ); $items['admin/content/simplenews/types/add'] = array( 'title' => 'Add newsletter', 'type' => MENU_LOCAL_TASK, 'page callback' => 'drupal_get_form', 'page arguments' => array('simplenews_admin_types_form'), 'access arguments' => array('administer newsletters'), 'file' => 'simplenews.admin.inc', 'weight' => -9, ); $items['admin/content/simplenews/subscriptions/delete'] = array( 'title' => 'Delete', 'type' => MENU_CALLBACK, 'page callback' => 'drupal_get_form', 'page arguments' => array('simplenews_subscription_multiple_delete_confirm'), 'access arguments' => array('administer newsletters'), 'file' => 'simplenews.admin.inc', ); $items['admin/content/simplenews/users'] = array( 'title' => 'Subscriptions', 'type' => MENU_LOCAL_TASK, 'page callback' => 'drupal_get_form', 'page arguments' => array('simplenews_subscription_admin'), 'access arguments' => array('administer newsletters'), 'file' => 'simplenews.admin.inc', 'weight' => -7, ); $items['admin/content/simplenews/users/edit/%'] = array( 'title' => 'Subscriptions', 'type' => MENU_CALLBACK, 'page callback' => 'drupal_get_form', 'page arguments' => array('simplenews_admin_users_form', 5), 'access arguments' => array('administer newsletters'), 'file' => 'simplenews.subscription.inc', ); $items['admin/content/simplenews/users/list'] = array( 'title' => 'List', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); $items['admin/content/simplenews/users/import'] = array( 'title' => 'Mass subscribe', 'type' => MENU_LOCAL_TASK, 'page callback' => 'drupal_get_form', 'page arguments' => array('simplenews_subscription_list_add'), 'access arguments' => array('administer newsletters'), 'file' => 'simplenews.admin.inc', 'weight' => -9, ); $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 newsletters'), 'file' => 'simplenews.admin.inc', 'weight' => -8, ); $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 newsletters'), 'file' => 'simplenews.admin.inc', 'weight' => -7, ); $items['admin/settings/simplenews'] = array( 'title' => 'Simplenews settings', 'description' => 'Manage simplenews configuration.', 'type' => MENU_NORMAL_ITEM, 'page callback' => 'drupal_get_form', 'page arguments' => array('simplenews_admin_settings'), 'access arguments' => array('administer simplenews settings'), 'file' => 'simplenews.admin.inc', ); $items['admin/settings/simplenews/general'] = array( 'title' => 'General', 'type' => MENU_DEFAULT_LOCAL_TASK, 'page arguments' => array('simplenews_admin_settings'), 'access arguments' => array('administer simplenews settings'), 'file' => 'simplenews.admin.inc', 'weight' => -10, ); $items['admin/settings/simplenews/newsletter'] = array( 'title' => 'Newsletter', 'type' => MENU_LOCAL_TASK, 'page arguments' => array('simplenews_admin_settings_newsletter'), 'access arguments' => array('administer simplenews settings'), 'weight' => -9, ); $items['admin/settings/simplenews/subscription'] = array( 'title' => 'Subscription', 'type' => MENU_LOCAL_TASK, 'page arguments' => array('simplenews_admin_settings_subscription'), 'access arguments' => array('administer simplenews settings'), 'weight' => -8, ); $items['admin/settings/simplenews/mail'] = array( 'title' => 'Mail backend', 'type' => MENU_LOCAL_TASK, 'page arguments' => array('simplenews_admin_settings_mail'), 'access arguments' => array('administer simplenews settings'), 'weight' => -7, ); $items['newsletter/confirm'] = array( 'title' => 'Confirm newsletter subscriptions', 'type' => MENU_CALLBACK, 'page callback' => 'simplenews_confirm_subscription', 'access arguments' => array('subscribe to newsletters'), 'file' => 'simplenews.subscription.inc', ); $items['newsletter/subscriptions'] = array( 'title' => 'Manage newsletter subscriptions', 'type' => MENU_CALLBACK, 'page callback' => 'drupal_get_form', 'page arguments' => array('simplenews_subscription_manager_form'), 'access arguments' => array('subscribe to newsletters'), 'file' => 'simplenews.subscription.inc', ); return $items; } /** * Implementation of hook_nodeapi(). */ function simplenews_nodeapi(&$node, $op, $teaser, $page) { // 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 'view': $node = simplenews_replace_vars($node, TRUE); break; case 'validate': global $_simplenews_valid_mails; if ($node->simplenews['send'] == SIMPLENEWS_COMMAND_SEND_NOW) { if (!isset($node->taxonomy)) { form_set_error('', t('You should assign content type %content_type to the !newsletter_vocabulary before you can send this newsletter.', array('%content_type' => $node->type, '!newsletter_vocabulary' => l(t('Newsletter vocabulary'), 'admin/content/taxonomy')))); } elseif (!simplenews_validate_taxonomy($node->taxonomy)) { form_set_error('taxonomy', t('You should select a newsletter term before you can send this newsletter issue.')); } } elseif ($node->simplenews['send'] == SIMPLENEWS_COMMAND_SEND_TEST) { $mails = explode(',', $node->simplenews['test_address']); foreach ($mails as $mail) { $mail = trim($mail); if ($mail == '') { form_set_error('simplenews][test_address', t('Cannot send test newsletter: no test e-mail address specified.')); } elseif (!valid_email_address($mail)) { form_set_error('simplenews][test_address', t('Cannot send test newsletter to %mail: e-mail address invalid.', array('%mail' => $mail))); } } } break; case 'presave': $term = simplenews_validate_taxonomy($node->taxonomy); $tid = is_array($term) ? array_values($term) : FALSE; $node->simplenews['tid'] = $tid ? $tid[0] : 0; break; case 'insert': case 'update': $send_with_permission = $node->simplenews['send'] == SIMPLENEWS_COMMAND_SEND_NOW && user_access('send newsletter'); if ($op == 'insert') { // Insert node $s_status = $send_with_permission ? SIMPLENEWS_STATUS_SEND_PENDING : SIMPLENEWS_STATUS_SEND_NOT; db_query("INSERT INTO {simplenews_newsletters} (nid, vid, tid, s_status, s_format, priority, receipt) VALUES (%d, %d, %d, %d, '%s', %d, %d)", $node->nid, $node->vid, $node->simplenews['tid'], $s_status, $node->simplenews['s_format'], $node->simplenews['priority'], $node->simplenews['receipt']); } else { // Update node if ($send_with_permission) { db_query("UPDATE {simplenews_newsletters} SET vid = %d, tid = %d, s_status = %d, s_format = '%s', priority = %d, receipt = %d WHERE nid = %d", $node->vid, $node->simplenews['tid'], SIMPLENEWS_STATUS_SEND_PENDING, $node->simplenews['s_format'], $node->simplenews['priority'], $node->simplenews['receipt'], $node->nid); } else { db_query("UPDATE {simplenews_newsletters} SET tid = %d, s_format = '%s', priority = %d, receipt = %d WHERE nid = %d", $node->simplenews['tid'], $node->simplenews['s_format'], $node->simplenews['priority'], $node->simplenews['receipt'], $node->nid); } } // When this node is selected for translation all translation of this node // will be send too. // All translated nodes will receive the same send states (priority, confirmation, format). if (module_exists('translation') && translation_supported_type($node->type) && $send_with_permission) { if ($translations = translation_node_get_translations($node->tnid)) { foreach ($translations as $translation) { db_query("UPDATE {simplenews_newsletters} SET s_status = %d, s_format = '%s', priority = %d, receipt = %d WHERE nid = %d", SIMPLENEWS_STATUS_SEND_PENDING, $node->simplenews['s_format'], $node->simplenews['priority'], $node->simplenews['receipt'], $translation->nid); } } } // Send newsletter or test newsletter if ($send_with_permission) { // Send newsletter to all subscribers simplenews_send_node($node); } elseif ($node->simplenews['send'] == SIMPLENEWS_COMMAND_SEND_TEST) { // Send test newsletter to test address(es) simplenews_send_test($node); } break; case 'delete': $result = db_query('DELETE FROM {simplenews_newsletters} WHERE nid = %d', $node->nid); if ($result) { drupal_set_message(t('Newsletter %title was successfully deleted.', array('%title' => $node->title))); } break; case 'load': $node->simplenews = db_fetch_array(db_query('SELECT * FROM {simplenews_newsletters} WHERE nid = %d', $node->nid)); break; } } /** * Implementation of hook_form(). */ function simplenews_form(&$node) { $type = node_get_types('type', $node); if ($type->has_title) { $form['title'] = array( '#type' => 'textfield', '#title' => check_plain($type->title_label), '#required' => TRUE, '#default_value' => $node->title, '#maxlength' => 255, '#weight' => -5, ); } if ($type->has_body) { $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count); // 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. $form['body_field']['body']['#description'] = t('This will be the body of your newsletter. Available variables are:') .' !site '. t('(the name of your website),') .' !uri '. t('(a link to your homepage),') .' !uri_brief '. t('(homepage link without the http://),') .' !date '. t("(today's date),") .' !login_uri '. t('(link to login page).'); } return $form; } /** * Validate if term is Newsletter taxonomy term. * * @return * Array of selected Newsletter terms. Example: array(4, 12) * FALSE: no Newsletter term is selected */ function simplenews_validate_taxonomy($taxonomy) { $vid = variable_get('simplenews_vid', ''); $result = db_query('SELECT tid FROM {term_data} WHERE vid = %d', $vid); while ($tid = db_fetch_object($result)) { $tids[] = $tid->tid; } if (isset($tids) && !empty($taxonomy)) { $taxes = array(); foreach ($taxonomy as $tax) { $taxes[] = $tax; } $selected_terms = array_intersect($tids, $taxes); return empty($selected_terms) ? FALSE : $selected_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, ); $form['content_types']['nodes']['#description'] = t('These content type(s) are used as newsletter. They can also be set in !simplenews_settings.', array('!simplenews_settings' => l('Simplenews settings', 'admin/settings/simplenews'))); } // Simplenews newsletter node form elseif (strpos($form_id, '_node_form')) { if (in_array($form['type']['#value'], variable_get('simplenews_content_types', array('simplenews')))) { if (isset($form['#node']->simplenews)) { $simplenews_values = $form['#node']->simplenews; } $vocabulary = taxonomy_vocabulary_load(variable_get('simplenews_vid', '')); if (!isset($vocabulary->nodes[$form['type']['#value']])) { drupal_set_message(t('Missing vocabulary %name. Please check the !settings.', array('%name' => $vocabulary->name, '!settings' => l('Simplenews settings', 'admin/settings/simplenews'))), 'error'); } $form['simplenews'] = array( '#type' => 'fieldset', '#title' => t('Newsletter sending options'), '#collapsible' => TRUE, '#collapsed' => FALSE, '#tree' => TRUE, ); // 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)) { // Hide format selection if there is nothing to choose. // The default format is plain text. if (count(_simplenews_format_options()) > 1) { $form['simplenews']['s_format'] = array( '#type' => 'select', '#title' => t('Format'), '#default_value' => $simplenews_values['s_format'], '#options' => _simplenews_format_options(), ); } else { $form['simplenews']['s_format'] = array( '#type' => 'hidden', '#value' => variable_get('simplenews_format', 'plain'), ); } $form['simplenews']['priority'] = array( '#type' => 'select', '#title' => t('Priority'), '#default_value' => isset($simplenews_values['priority']) ? $simplenews_values['priority'] : variable_get('simplenews_priority', SIMPLENEWS_PRIORITY_NONE), '#options' => array( SIMPLENEWS_PRIORITY_NONE => t('none'), SIMPLENEWS_PRIORITY_HIGHEST => t('highest'), SIMPLENEWS_PRIORITY_HIGH => t('high'), SIMPLENEWS_PRIORITY_NORMAL => t('normal'), SIMPLENEWS_PRIORITY_LOW => t('low'), SIMPLENEWS_PRIORITY_LOWEST => t('lowest'), ), ); $form['simplenews']['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), ); if (user_access('send newsletter')) { $options[SIMPLENEWS_COMMAND_SEND_NONE] = t("Don't send now"); $options[SIMPLENEWS_COMMAND_SEND_TEST] = t('Send one test newsletter to the test address'); $options[SIMPLENEWS_COMMAND_SEND_NOW] = t('Send newsletter'); $form['simplenews']['send'] = array( '#type' => 'radios', '#title' => t('Sending'), '#default_value' => isset($simplenews_values['send']) ? $simplenews_values['send'] : variable_get('simplenews_send', SIMPLENEWS_COMMAND_SEND_NONE), '#options' => $options, ); } else { $options[SIMPLENEWS_COMMAND_SEND_NONE] = t("Don't send now"); $options[SIMPLENEWS_COMMAND_SEND_TEST] = t('Send one test newsletter to the test address'); $form['simplenews']['send'] = array( '#type' => 'radios', '#title' => t('Sending'), '#default_value' => isset($simplenews_values['send']) ? $simplenews_values['send'] : SIMPLENEWS_COMMAND_SEND_NONE, '#options' => $options, '#description' => t('You have no privileges to send this newsletter'), ); } if (module_exists('translation') && translation_supported_type($form['#node']->type)) { $form['simplenews']['send']['#description'] = t('This newsletter issue is part of a translation set. All translations in this set will be send when you send this node.'); } $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 e-mail addresses'), '#description' => t('Supply a comma-separated list of e-mail 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), ); } } else { $form['simplenews']['none'] = array( '#type' => 'checkbox', '#title' => t('This newsletter has been sent'), '#return_value' => 0, '#attributes' => array('checked' => 'checked', 'disabled' => 'disabled'), ); } $form['simplenews']['s_status'] = array( '#type' => 'hidden', '#value' => isset($simplenews_values['s_status']) ? $simplenews_values['s_status'] : SIMPLENEWS_STATUS_SEND_NOT, ); } } } /** * Implementation of hook_cron(). */ function simplenews_cron() { simplenews_mail_send(); simplenews_clear_spool(); } /** * 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 ($term) { case 'term': db_query('DELETE FROM {simplenews_snid_tid} WHERE tid = %d', $array['tid']); db_query("DELETE FROM {blocks} WHERE module = '%s' AND delta = '%s'", 'simplenews', $term['tid']); drupal_set_message(t('Deleted all subscriptions to newsletter %newsletter.', array('%newsletter' => $term['name']))); break; } } } /** * Implementation of hook_user(). * * Checks whether an email address is subscribed to the newsletter when a new * user signs up. If so, changes uid from 0 to the new uid in * simplenews_subscriptions so that the user's subscription status is known when * he logs in. */ function simplenews_user($op, &$edit, &$account, $category = NULL) { switch ($op) { case 'insert': if ($edit['mail']) { $query = "SELECT snid FROM {simplenews_subscriptions} WHERE mail = '%s'"; if ($result = db_fetch_object(db_query($query, $edit['mail']))) { db_query("UPDATE {simplenews_subscriptions} SET uid = %d, language = '%s' WHERE snid = %d", $edit['uid'], $edit['language'], $result->snid); } } break; case 'update': if ($category == 'account' && $edit['mail']) { $query = "SELECT snid FROM {simplenews_subscriptions} WHERE uid = %d"; if ($result = db_fetch_object(db_query($query, $account->uid))) { db_query("DELETE FROM {simplenews_subscriptions} WHERE mail = '%s' AND uid = %d", $edit['mail'], 0); db_query("UPDATE {simplenews_subscriptions} SET mail = '%s', language = '%s' WHERE snid = %d", $edit['mail'], $edit['language'], $result->snid); } else { $query = "SELECT snid FROM {simplenews_subscriptions} WHERE mail = '%s'"; if ($result = db_fetch_object(db_query($query, $edit['mail']))) { db_query("UPDATE {simplenews_subscriptions} SET uid = %d, language = '%s' WHERE snid = %d", $account->uid, $account->language, $result->snid); } } } // Activate/deactivate subscription when account is blocked/unblocked if ($category == 'account' && isset($edit['status'])) { if (variable_get('simplenews_sync_account', TRUE)) { db_query("UPDATE {simplenews_subscriptions} SET activated = %d WHERE uid = %d", $edit['status'], $account->uid); } } if ($category == 'newsletter' && user_access('subscribe to newsletters')) { foreach ($edit['newsletters'] as $tid => $checked) { if ($checked) { simplenews_subscribe_user($account->mail, $tid, FALSE); } else { simplenews_unsubscribe_user($account->mail, $tid, FALSE); } } } break; case 'delete': if (variable_get('simplenews_sync_account', TRUE)) { // Delete subscription and all newsletter subscriptions when account is removed. // We don't use simplenews_get_subscription() here because the user is already // deleted from the {user} table. $snid = db_result(db_query("SELECT s.snid FROM {simplenews_subscriptions} s WHERE s.mail = '%s'", $account->mail)); db_query('DELETE FROM {simplenews_snid_tid} WHERE snid = %d', $snid); db_query('DELETE FROM {simplenews_subscriptions} WHERE snid = %d', $snid); } else { // Only remove uid from subscription data when account is removed db_query("UPDATE {simplenews_subscriptions} SET uid = 0 WHERE uid = %d", $account->uid); } break; case 'form': if ($category == 'newsletter' && user_access('subscribe to newsletters')) { $subscription = simplenews_get_subscription($account); $form = _simplenews_subscription_manager_form($subscription); $form['subscriptions']['#title'] = t('Current newsletter subscriptions'); unset($form['update'], $form['subscriptions']['mail']); return $form; } break; case 'categories': if (user_access('subscribe to newsletters')) { $output[] = array('name' => 'newsletter', 'title' => t('My newsletters'), 'weight' => 10); } return $output; case 'view': global $user; if ($user->uid == $account->uid || user_access('administer users')) { $account->content['simplenews'] = array( '#type' => 'user_profile_category', '#title' => t('Newsletters'), ); $tree = taxonomy_get_tree(variable_get('simplenews_vid', '')); foreach ($tree as $newsletter) { if (db_result(db_query('SELECT COUNT(s.uid) FROM {simplenews_subscriptions} s INNER JOIN {simplenews_snid_tid} t ON s.snid = t.snid WHERE s.uid = %d AND t.tid = %d', $account->uid, $newsletter->tid))) { $subscriptions[] = l($newsletter->name, 'taxonomy/term/'. $newsletter->tid); } } if (isset($subscriptions)) { $subscriptions = implode(', ', $subscriptions); } else { $subscriptions = t('None'); } // When a user has no permission to subscribe and is not subscribed // we do not display the 'no subscriptions' message. if (user_access('subscribe to newsletters') || $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->content['simplenews']['my_newsletters'] = array( '#type' => 'user_profile_item', '#value' => t('Manage !my_subscriptions', array('!my_subscriptions' => l(t('my subscriptions'), 'user/'. $account->uid .'/edit/newsletter'))), '#weight' => -1, ); } return array(t('Newsletters') => $items); } break; } } /** * Implementation of hook_block(). */ function simplenews_block($op = 'list', $delta = 0, $edit = array()) { switch ($op) { case 'list': $blocks = array(); foreach (taxonomy_get_tree(variable_get('simplenews_vid', '')) as $newsletter) { //TODO: 1. without form -> by role; 2. with form -> user caching with refresh on subscribe/unsubscribe (option as setting) or no caching $blocks[$newsletter->tid] = array( 'info' => t('Newsletter: @title', array('@title' => $newsletter->name)), 'cache' => variable_get('simplenews_block_f_'. $newsletter->tid, 1) ? BLOCK_NO_CACHE : BLOCK_CACHE_PER_ROLE, ); } return $blocks; case 'configure': $form['simplenews_block_'. $delta]['simplenews_block_m_status_'. $delta] = array( '#type' => 'checkbox', '#title' => t('Display block message'), '#return_value' => 1, '#default_value' => variable_get('simplenews_block_m_status_'. $delta, 1), ); $form['simplenews_block_'. $delta]['simplenews_block_m_'. $delta] = array( '#type' => 'textfield', '#title' => t('Block message'), '#size' => 60, '#maxlength' => 128, '#default_value' => variable_get('simplenews_block_m_'. $delta, t('Stay informed on our latest news!')), ); $form['simplenews_block_'. $delta]['simplenews_block_f_'. $delta] = array( '#type' => 'checkbox', '#title' => t('Display subscription form'), '#return_value' => 1, '#description' => t('If selected a subscription form is displayed, if not selected a link to the subscription page is displayed.'), '#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), '#description' => t('Links (to previous issues, previous issues and RSS-feed) are only displayed to users who have "view links in block" privileges.'), ); $form['simplenews_block_'. $delta]['simplenews_block_i_status_'. $delta] = array( '#type' => 'checkbox', '#title' => t('Display previous issues'), '#return_value' => 1, '#default_value' => variable_get('simplenews_block_i_status_'. $delta, 0), ); $form['simplenews_block_'. $delta]['simplenews_block_i_'. $delta] = array( '#type' => 'select', '#title' => t('Number of issues to display'), '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)), '#default_value' => variable_get('simplenews_block_i_'. $delta, 5), ); $form['simplenews_block_'. $delta]['simplenews_block_r_'. $delta] = array( '#type' => 'checkbox', '#title' => t('Display RSS-feed icon'), '#return_value' => 1, '#default_value' => variable_get('simplenews_block_r_'. $delta, 1), ); return $form; case 'save': variable_set('simplenews_block_m_status_'. $delta, $edit['simplenews_block_m_status_'. $delta]); 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': if ($newsletter = (array) taxonomy_get_term($delta)) { $block = array( 'subject' => check_plain($newsletter['name']), 'content' => theme('simplenews_block', $newsletter['tid']), ); } return $block; } } /** * 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() { foreach (taxonomy_get_tree(variable_get('simplenews_vid', '')) as $newsletter) { $forms['simplenews_block_form_'. $newsletter->tid] = array( 'callback' => 'simplenews_block_form', 'callback arguments' => array($newsletter->tid), ); } return $forms; } /** * Load a user or creates a dummy anonymous user. * * @return account * object ( * mail, e-mail address * uid, uid or 0 for anonymous * ) */ function _simplenews_user_load($mail) { $account = user_load(array('mail' => $mail)); if ($account === FALSE) { // Construct anonymous user since we don't have a user that matches that e-amil. $account = new stdClass(); $account->uid = 0; $account->mail = $mail; } return $account; } /** * Subscribe a user to a newsletter or send a confirmation mail. * * The $confirm parameter determines the action: * FALSE = The user is subscribed * TRUE = User receives an email to verify the address and complete the subscription * A new subscription account is created when the user is subscribed to the first newsletter * * @param string $mail * The e-mail address to subscribe to the newsletter. * @param integer $tid * The term ID of the newsletter. * @param boolean $confirm * TRUE = send confirmation mail; FALSE = subscribe immediate to the newsletter * @param string $preferred_language * The language code (i.e. 'en', 'nl') of the user preferred language. * Use '' for the site default language. * Use NULL for the language of the current page. */ function simplenews_subscribe_user($mail, $tid, $confirm = TRUE, $preferred_language = NULL) { global $language; //Prevent mismatches from accidental capitals in mail address $mail = strtolower($mail); // Get current subscriptions if any. $account = (object) array('mail' => $mail); $subscription = simplenews_get_subscription($account); // If user is not subscribed to ANY newsletter, create a subscription account if ($subscription->snid == 0) { // To subscribe a user: // - Fetch the users uid. // - Determine the user preferred language. // - Add the user to the database. // - Get the full subscription object based on the mail address. // Note that step 3 gets subscription data based on mail address because the uid can be 0 (for anonymous users) $account = _simplenews_user_load($mail); // If the site is multilingual: // - Anonymous users are subscribed with their preferred language // equal to the language of the current page. // - Registered users will be subscribed with their default language as // set in their account settings. // By default the preferred language is not set. if (variable_get('language_count', 1) > 1) { if ($account->uid) { $preferred_language = $account->language; } else { $preferred_language = isset($preferred_language) ? $preferred_language : $language->language; } } db_query("INSERT INTO {simplenews_subscriptions} (mail, uid, language, activated) VALUES ('%s', %d, '%s', %d)", $mail, $account->uid, $preferred_language, 1); $subscription = simplenews_get_subscription($account); } if ($confirm) { // Send confirmation e-mail 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['newsletter'] = taxonomy_get_term($tid); $params['context']['account'] = $subscription; drupal_mail('simplenews', 'subscribe', $mail, $subscription->language, $params, $params['from']['address']); } elseif (!isset($subscription->tids[$tid])) { // OR add user to newsletter relationship if not already subscribed. db_query("INSERT INTO {simplenews_snid_tid} (snid, tid) VALUES (%d, %d)", $subscription->snid, $tid); // Execute simplenews subscribe trigger. simplenews_call_actions('subscribe', $subscription); } return TRUE; } /** * Unsubscribe a user from a newsletter or send a confirmation mail. * * The $confirm parameter determines the action: * FALSE = The user is unsubscribed * TRUE = User receives an email to verify the address and complete the unsubscription * The subscription account is deleted when the user is unsubscribed to the last newsletter * * @param string $mail The e-mail address to unsubscribe from the newsletter. * @param integer $tid The term ID of the newsletter. * @param boolean $confirm TRUE = send confirmation mail; FALSE = unsubscribe immediate from the newsletter */ function simplenews_unsubscribe_user($mail, $tid, $confirm = TRUE) { //Prevent mismatches from accidental capitals in mail address $mail = strtolower($mail); $account = (object) array('mail' => $mail); $subscription = simplenews_get_subscription($account); // The unlikely case that a user is subscribed from a non existing newsletter is logged if (!$newsletter = taxonomy_get_term($tid)) { watchdog('simplenews', 'Attempt to unsubscribe from non existing newsletter term ID %id', array('%id' => $tid), WATCHDOG_ERROR); return FALSE; } if ($confirm) { // Send confirmation e-mail to user to complete unsubscription // or to tell them that he or she is not subscribed // Confirmation mail is in the user preferred language. $params['from'] = _simplenews_set_from(); $params['newsletter'] = $newsletter; $params['context']['account'] = $subscription; drupal_mail('simplenews', 'unsubscribe', $mail, $subscription->language, $params, $params['from']['address']); } elseif (isset($subscription->tids[$tid])) { // OR remove the user from the newsletter. db_query('DELETE FROM {simplenews_snid_tid} WHERE snid = %d AND tid = %d', $subscription->snid, $tid); // Clean up subscription account if user is not subscribed to any newsletter anymore if (!db_result(db_query("SELECT COUNT(*) FROM {simplenews_snid_tid} t WHERE t.snid = %d", $subscription->snid))) { db_query('DELETE FROM {simplenews_subscriptions} WHERE snid = %d', $subscription->snid); } // Execute simplenews unsubscribe trigger simplenews_call_actions('unsubscribe', $subscription); } return TRUE; } /** * Check if the e-mail address is subscribed to the given newsletter. * * @param string $mail email address * @param integer $tid newsletter term id * * @return boolean TRUE = email address is subscribed to given newsletter term id */ function simplenews_user_is_subscribed($mail, $tid) { return db_result(db_query("SELECT COUNT(*) FROM {simplenews_subscriptions} s INNER JOIN {simplenews_snid_tid} t ON s.snid = t.snid WHERE s.mail = '%s' AND t.tid = %d", $mail, $tid)) ? TRUE : FALSE; } /** * Get the subscription object for the given account. * * Account is defined by (in order of preference) snid, e-mail 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 : e-mail address * uid : user id * ) * * @return subscription object * object( * snid : subscription id. 0 if account is not subscribed * tids : array of newsletter tid's * uid : user id. 0 if account is anonymous user * mail : user e-mail address. empty if e-mail is unknown * name : always empty. Added for compatebility with user account object * language : language object. User preferred or default language * ) */ function simplenews_get_subscription($account) { // Load subscription data based on available account informatioin // NOTE that the order of checking for snid, mail and uid is critical. mail must be checked *before* uid. See simplenews_subscribe_user() if (isset($account->snid)) { $subscription = db_fetch_object(db_query("SELECT s.*, u.language FROM {simplenews_subscriptions} s INNER JOIN {users} u ON u.uid = s.uid WHERE s.snid = %d", $account->snid)); } elseif (isset($account->mail)) { $subscription = db_fetch_object(db_query("SELECT s.*, u.language FROM {simplenews_subscriptions} s INNER JOIN {users} u ON u.uid = s.uid WHERE s.mail = '%s'", $account->mail)); } elseif (isset($account->uid) && $account->uid > 0) { $subscription = db_fetch_object(db_query("SELECT s.*, u.language FROM {simplenews_subscriptions} s INNER JOIN {users} u ON u.uid = s.uid WHERE s.uid = %d", $account->uid)); } if ($subscription) { $result = db_query("SELECT tid FROM {simplenews_snid_tid} t WHERE t.snid = %d", $subscription->snid); $subscription->tids = array(); while ($newsletter = db_fetch_object($result)) { $subscription->tids[$newsletter->tid] = $newsletter->tid; } $subscription->name = ''; $subscription->language = user_preferred_language($subscription, language_default()); } else { // Account is unknown in subscription table. Create default subscription object $subscription = new stdClass(); $subscription->name = ''; $subscription->uid = isset($account->uid) ? $account->uid : 0; $subscription->mail = isset($account->mail) ? $account->mail : ''; $subscription->language = language_default(); $subscription->snid = 0; $subscription->tids = array(); } return $subscription; } /** * Delete every subscription for the given subscription ID. * * @param integer $snid subscription id */ function simplenews_delete_subscription($snid) { $account = db_fetch_object(db_query('SELECT mail FROM {simplenews_subscriptions} WHERE snid = %d', $snid)); db_query('DELETE FROM {simplenews_subscriptions} WHERE snid = %d', $snid); db_query('DELETE FROM {simplenews_snid_tid} WHERE snid = %d', $snid); watchdog('simplenews', 'User %email deleted from the mailing list.', array('%email' => $account->mail), WATCHDOG_NOTICE); } /** * Build subscription manager form. * * @param object $subscription subscription object */ function _simplenews_subscription_manager_form($subscription) { $form = array(); $options = array(); foreach (taxonomy_get_tree(variable_get('simplenews_vid', '')) as $newsletter) { $options[$newsletter->tid] = check_plain($newsletter->name); $default_value[$newsletter->tid] = FALSE; } $form['subscriptions'] = array( '#type' => 'fieldset', '#description' => t('Select the newsletter(s) to which you want to subscribe or unsubscribe.'), ); $form['subscriptions']['newsletters'] = array( '#type' => 'checkboxes', '#options' => $options, '#default_value' => array_merge($default_value, (array)$subscription->tids), ); // If current user is logged in, just display e-mail. // Anonymous users see an e-mail box and will receive confirmations if ($subscription->mail) { $form['subscriptions']['#title'] = t('Subscriptions for %mail', array('%mail' => $subscription->mail)); $form['subscriptions']['mail'] = array('#type' => 'value', '#value' => $subscription->mail); $form['update'] = array( '#type' => 'submit', '#value' => t('Update'), '#weight' => 20, ); } else { $form['subscriptions']['#title'] = t('Manage your newsletter subscriptions'); $form['subscriptions']['mail'] = array( '#type' => 'textfield', '#title' => t('E-mail'), '#size' => 20, '#maxlength' => 128, '#weight' => 10, '#required' => TRUE, ); $form['subscribe'] = array( '#type' => 'submit', '#value' => t('Subscribe'), '#weight' => 20, ); $form['unsubscribe'] = array( '#type' => 'submit', '#value' => t('Unsubscribe'), '#weight' => 30, ); } return $form; } /** * Create a list of recent newsletters. * * @param integer $tid term id of selected newsletter * @param integer $count number of newsletters in the list */ function simplenews_recent_newsletters($tid, $count = 5) { $result = db_query_range(db_rewrite_sql('SELECT n.nid, n.title, sn.s_status FROM {node} n INNER JOIN {term_node} t ON n.vid = t.vid INNER JOIN {simplenews_newsletters} sn ON n.nid = sn.nid WHERE (t.tid = %d AND n.status = %d) ORDER BY n.created DESC'), $tid, 1, 0, $count); $titles = array(); while ($item = db_fetch_object($result)) { $titles[$item->nid]['data'] = l($item->title, 'node/'. $item->nid); $titles[$item->nid]['class'] = ($item->s_status == SIMPLENEWS_STATUS_SEND_NOT) ? 'newsletter-created' : 'newsletter-send'; } return $titles; } /** * Newsletter (un)subscription form for authenticated and anonymous users. * * @param $tid term id of selected newsletter. * * @see simplenews_block_form_validate() * @see simplenews_block_form_submit() */ function simplenews_block_form(&$form_state, $tid) { global $user; $form = array(); if ($user->uid) { if (simplenews_user_is_subscribed($user->mail, $tid)) { $submit_text = t('Unsubscribe'); $form['action'] = array('#type' => 'value', '#value' => 'unsubscribe'); } else { $submit_text = t('Subscribe'); $form['action'] = array('#type' => 'value', '#value' => 'subscribe'); } $form['display_mail'] = array( '#type' => 'item', '#title' => t('User'), '#value' => check_plain($user->name), ); $form['mail'] = array('#type' => 'value', '#value' => $user->mail); } else { $form['mail'] = array( '#type' => 'textfield', '#title' => t('E-mail'), '#size' => 20, '#maxlength' => 128, '#required' => TRUE, ); $form['action'] = array( '#type' => 'radios', '#default_value' => 'subscribe', '#options' => array('subscribe' => t('Subscribe'), 'unsubscribe' => t('Unsubscribe')), ); } // All block forms use the same validate and submit function. // #tid carries the tid for processing of the right newsletter issue term. $form['#tid'] = $tid; $form['#validate'][] = 'simplenews_block_form_validate'; $form['#submit'][] = 'simplenews_block_form_submit'; $form['submit'] = array( '#type' => 'submit', '#value' => isset($submit_text) ? $submit_text : t('Save') ); return $form; } function simplenews_block_form_validate($form, &$form_state) { if (!valid_email_address($form_state['values']['mail'])) { form_set_error('mail', t("The e-mail address you supplied is not valid.")); } } function simplenews_block_form_submit($form, &$form_state) { global $user; $tid = $form['#tid']; $account = _simplenews_user_load($form_state['values']['mail']); // If e-mail belongs to the current registered user, don't send confirmation. $confirm = $account->uid && $account->uid == $user->uid ? FALSE : TRUE; switch ($form_state['values']['action']) { case 'subscribe': simplenews_subscribe_user($form_state['values']['mail'], $tid, $confirm); if ($confirm) { drupal_set_message(t('You will receive a confirmation e-mail shortly containing further instructions on how to complete your subscription.')); } else { drupal_set_message(t('You have been successfully subscribed.')); } break; case 'unsubscribe': simplenews_unsubscribe_user($form_state['values']['mail'], $tid, $confirm); if ($confirm) { drupal_set_message(t('You will receive a confirmation e-mail shortly containing further instructions on how to complete the unsubscription process.')); } else { drupal_set_message(t('You have been successfully unsubscribed.')); } break; } } /** * Send newsletter node to subcribers. * * @param integer or object $node Newsletter node to be send. integer = nid; object = node object * @param array $accounts account objects to send the newsletter to. * account = object ( * snid = subscription id. 0 if no subscription record exists * tids = array(tid) array of newsletter tid's * uid = user id. 0 if subscriber is anonymous user. * mail = user e-mail address. * name = . Added for compatebility with user account object * language = language object. User preferred of default language * ) * NOTE: either snid, mail or uid is required. */ function simplenews_send_node($node, $accounts = array()) { $mails = array(); if (is_numeric($node)) { $node = node_load($node); } if (is_object($node)) { $from = _simplenews_set_from($node); $params['context']['node'] = $node; $params['from'] = $from; $node_data['tid'] = $node->simplenews['tid']; $node_data['nid'] = $node->nid; $node_data['vid'] = $node->vid; if (empty($accounts)) { // No accounts specified. Get email address of all accounts subscribed to this newsletter. $result = db_query('SELECT s.mail FROM {simplenews_subscriptions} s INNER JOIN {simplenews_snid_tid} t ON s.snid = t.snid WHERE s.activated = %d AND t.tid = %d', 1, $node_data['tid']); while ($account = db_fetch_object($result)) { $mails[] = array('mail' => $account->mail); } } else { // Get email address of specified accounts. foreach ($accounts as $account) { $account = simplenews_get_subscription($account); $mails[] = array('mail' => $account->mail); } } // When cron is not used (simplenews_use_cron = FALSE) the spool content // is send immediately after filling the spool. When cron is used spooled // emails are send on each cron run. foreach ($mails as $mail) { $data = array_merge($node_data, $mail); simplenews_mail_spool_set($data); } if (variable_get('simplenews_use_cron', TRUE) == FALSE) { simplenews_mail_send($node_data['nid'], $node_data['vid'], 999999); simplenews_clear_spool(); } } } /** * Send test version of newsletter. * * @param integer or object $node Newsletter node to be send. Integer = nid; Object = node object * @param array $accounts account objects to send the test newsletter to. * account = object( * mail : e-mail address of recipient * ) */ function simplenews_send_test($node, $accounts = array()) { if (is_numeric($node)) { $node = node_load($node); } if (is_object($node)) { $from = _simplenews_set_from($node); $params['context']['node'] = $node; $params['from'] = $from; // If no account is specified the test newsletter is send to the test address(es) specified in the node. if (empty($accounts)) { // Build array of test e-mail addresses $mails = explode(',', $node->simplenews['test_address']); foreach ($mails as $mail) { $account['mail'] = trim($mail); $accounts[] = (object)$account; } } // Test emails are send directly by drupal_mail(). This in contrast to simplenews_send_node() which send via a buffer. foreach ($accounts as $account) { $subscription = simplenews_get_subscription($account); $params['context']['account'] = $subscription; $result = drupal_mail('simplenews', 'test', $account->mail, $subscription->language, $params, $from['address']); if ($result['result']) { drupal_set_message(t('Test newsletter sent to %recipient.', array('%recipient' => $account->mail))); } } } } /** * 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] * @param array $params parameter array * [context][node] : node object of message to be send * [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 ') * [newsletter] : newsletter object (tid, name) * [tokens] : tokens for variable replacement. Defaults to: user_mail_tokens() */ function simplenews_mail($key, &$message, $params) { $context = $params['context']; switch ($key) { case 'node': case 'test': // Message header, body and mail headers are buffered to increase // perfomance when sending multiple mails. Buffered data only contains // general data, no recipient specific content. Placeholders are used // for recipient data and will later be replaced. // When mailing multiple newsletters in one page call or cron run, // data is once stored and subsequently retreived from the // static $messages variable. // $message buffer is node and language specific. static $messages = array(); $nid = $context['node']->nid; $langcode = $message['language']->language; if (module_exists('translation')) { // If the node has translations and a translation is required // the equivalent of the node in the required langugage is used // or the base node (nid == tnid) is used if ($tnid = $context['node']->tnid) { if ($langcode != $context['node']->language) { $translations = translation_node_get_translations($tnid); if ($translation = $translations[$langcode]) { $nid = $translation->nid; $langcode = $translation->language; } else { // No translation found which matches the required language // Base node ($tnid) is used. The matching language replaces // the required language. foreach ($translations as $translation) { if ($translation->nid == $tnid) { $nid = $tnid; $langcode = $translation->language; break; } } } } } // If a different node is selected and this node is not available in // the message buffer, the selected node load will replace the original. if ($nid != $context['node']->nid && !isset($messages[$nid][$langcode])) { $context['node'] = node_load($nid); } } if (!isset($messages[$nid][$langcode])) { $node = $context['node']; // Add simplenews specific header data $headers = array_merge($message['headers'], _simplenews_headers($node, $params['from']['address'])); $headers['From'] = $params['from']['formatted']; $message['headers'] = $messages[$nid][$langcode]['headers'] = $headers; // Build email subject if ($tid = $node->simplenews['tid']) { $term = taxonomy_get_term($tid); // Translate the newsletter term name if simplenews vocabulary uses Localized terms. if (module_exists('i18ntaxonomy') && i18ntaxonomy_vocabulary(variable_get('simplenews_vid', '')) == I18N_TAXONOMY_LOCALIZE) { $name = check_plain(tt('taxonomy:term:'. $tid .':name', $term->name, $langcode)); } else { $name = check_plain($term->name); } } else { $name = t('Unassigned newsletter'); } $subject = theme('simplenews_newsletter_subject', $name, check_plain($node->title), $message['language']); $subject = str_replace(array("\r", "\n"), '', $subject); $message['subject'] = $messages[$nid][$langcode]['subject'] = $subject; // Build message body // Processing node body mimics node_view() with full node view $node = node_build_content($node, FALSE, TRUE); $content = drupal_render($node->content); $node->body = $content; unset($node->teaser); node_invoke_nodeapi($node, 'alter', FALSE, TRUE); $node = node_prepare($node); $body = theme('simplenews_newsletter_body', $node->body, check_plain($node->title), $message['language']); // Buffer body text node and language specific $messages[$nid][$langcode]['body'] = $body; } else { // Get message data from buffer $message['headers'] = $messages[$nid][$langcode]['headers']; $message['subject'] = $messages[$nid][$langcode]['subject']; $body = $messages[$nid][$langcode]['body']; } // Build message body. // The placeholders are replaced with user specific data // before conversion to plain text. $variables = user_mail_tokens($context['account'], $context['account']->language); $body = strtr($body, $variables); $body = simplenews_html_to_text($body); $message['body']['body'] = $body; // Build message footer. if ($key == 'node' && isset($context['account']->snid)) { $hash = _simplenews_generate_hash($context['account']->mail, $context['account']->snid, $context['node']->simplenews['tid']); } else { $hash = ''; } $message['body']['footer'] = theme('simplenews_newsletter_footer', $node->simplenews['s_format'], $hash, $key == 'test', $message['language']); // Add user specific header data. $message['headers']['List-Unsubscribe'] = '<'. url('newsletter/confirm/remove/'. $hash, array('absolute' => TRUE)) .'>'; break; case 'subscribe': // Use formatted from address "name" $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/add/'. $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('subscribe_subscribed', $context['account']->language, $variables); } else { $message['body'] = _simplenews_subscription_confirmation_text('subscribe_unsubscribed', $context['account']->language, $variables); } break; case 'unsubscribe': // Use formatted from address "name" $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); } } } /** * Call simplenews actions * * @see simplenews_hook_info() */ 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 e-mail 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 e-mail 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 sends html or plain text newsletters to the mailing list. Subscription and unsubscription are managed through a block, a form or by the newsletter administrator.') ."
\n"; $help .= t('Individual newsletters are grouped by a newsletter taxonomy term. Newsletters can have a block with the ability of (un)subscription, listing of recent newsletters and an associated rss-feed.') ."
\n"; $help .= t('Sending of large mailings can be managed by cron.') ."

\n"; $help .= "

". t('For more information please read the configuration and customization handbook Simplenews page.', array('@handbook', 'http://drupal.org/node/197057')) ."

\n"; return $help; case 'node/add/simplenews': $help = "

". t('Add this newsletter issue to a newsletter by selecting a newsletter from the select list.') ."

\n"; $help .= "

". t('Send a newsletter or a test newsletter by selecting the appropriate radio button and submitting the node.') ."

\n"; if (!module_exists('mimemail')) { $help .= "

". t('Install Mime Mail module to send HTML emails. Mime Mail is also used to send emails with attachments, both plain text and HTML emails.') ."

\n"; } return $help; case 'admin/settings/simplenews/newsletter': $help = '

'. t('Newsletter specific settings can be found at the Newsletters settings page.', array('@page' => url('admin/content/simplenews/types'))) ."

\n"; 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; case 'admin/content/simplenews/types/edit/%': $help = '

'. t('This newsletter issue is based on a taxonomy term. More settings for this term can be found at Edit Taxonomy term.', array('@edit_term' => url('admin/content/taxonomy/edit/term/'. $arg[5]))) ."

\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 strtr($admin_setting, $variables); } else { // No override, return default string. switch ($key) { case 'subscribe_unsubscribed': return t("We have received a request for subscription of !mailto to the !newsletter 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']; // 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_message'] = variable_get('simplenews_block_m_status_'. $tid, 1); $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'] = (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 e-mail 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 = '

--
'. l(t('Click here to unsubscribe from this newsletter', array(), $language->language), 'newsletter/confirm/remove/'. $hash, array('html' => TRUE, 'language' => $language)) .'

'; } 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; }