You are here

notify.module in Notify 5

File

notify.module
View source
<?php

/**
 * Implementation of hook_help().
 */
function notify_help($section) {
  switch ($section) {
    case 'admin/help#notify':
      $output = '<p>' . t('The notification module allows users to subscribe to periodic e-mails which include all new or revised content and/or comments much like the daily news letters sent by some websites.  Even if this feature is not configured for normal site users, it can be a useful feature for an administrator of a site to monitor content submissions and comment posts.') . '</p>';
      $output .= '<p>' . t('The administrator sets the frequency of the e-mails in the notify administration interface. They can also set how many e-mail failures should occur before notify stops sending notifications.  Note that cron must be enabled for notifications to be sent out.') . '</p>';
      $output .= t('<p>You can</p><ul><li>set up your site to run tasks automatically at required intervals. For more information, see <a href="@admin-help-system">cron</a>.</li><li>administer notify <a href="@admin-settings-notify">administer &gt;&gt; settings &gt;&gt; notify</a>.</li></ul>', array(
        '@admin-help-system' => url('admin/help/system'),
        '@admin-settings-notify' => url('admin/settings/notify'),
      ));
      $output .= '<p>' . t('For more information please read the configuration and customization handbook <a href="@notify">Notify page</a>.', array(
        '@notify' => 'http://www.drupal.org/handbook/modules/notify/',
      )) . '</p>';
      return $output;
  }
}

/**
 * Menu callback; display notify settings page.
 */
function notify_admin_settings() {
  $period = array(
    900 => format_interval(900),
    1800 => format_interval(1800),
    3600 => format_interval(3600),
    10800 => format_interval(10800),
    21600 => format_interval(21600),
    32400 => format_interval(32400),
    43200 => format_interval(43200),
    86400 => format_interval(86400),
    172800 => format_interval(172800),
    259200 => format_interval(259200),
    604800 => format_interval(604800),
    1209600 => format_interval(1209600),
    2419200 => format_interval(2419200),
    -1 => t('Never'),
  );
  $form = array();
  $form['notify_send'] = array(
    '#type' => 'select',
    '#title' => t('Send notifications every'),
    '#default_value' => variable_get('notify_send', 86400),
    '#options' => $period,
    '#description' => t('How often should new content notifications be sent? Requires cron to be running.'),
  );
  $form['notify_send_hour'] = array(
    '#type' => 'select',
    '#title' => t('Send notifications during this hour'),
    '#default_value' => variable_get('notify_send_hour', date('G', variable_get('notify_send_last', 0))),
    '#options' => array(
      0,
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9,
      10,
      11,
      12,
      13,
      14,
      15,
      16,
      17,
      18,
      19,
      20,
      21,
      22,
      23,
    ),
    '#description' => t('Hour of the day to send noticiations, on 24 hour clock.'),
  );
  $form['notify_attempts'] = array(
    '#type' => 'select',
    '#title' => t('Number of failed sends after which notifications are disabled'),
    '#default_value' => variable_get('notify_attempts', 5),
    '#options' => array(
      t('Disabled'),
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9,
      10,
      15,
      20,
    ),
  );
  return system_settings_form($form);
}

/**
 * Implementation of hook_cron().
 */
function notify_cron() {
  $send_last = variable_get('notify_send_last', 0);
  $send_interval = variable_get('notify_send', 86400);
  $send_hour = variable_get('notify_send_hour', date('G', variable_get('notify_send_last', 0)));
  if (time() - $send_last > $send_interval && (date('G', time()) >= $send_hour || $send_interval < 86400) && $send_interval != -1) {
    _notify_send();
    variable_set('notify_send_last', time());
  }
}

/**
 * Implementation of hook_user().
 */
function notify_user($type, &$edit, &$user, $category = NULL) {
  switch ($type) {
    case 'delete':
      db_query('DELETE FROM {notify} WHERE uid = %d', $user->uid);
      break;
  }
}

/**
 * Implementation of hook_perm().
 */
function notify_perm() {
  return array(
    'access notify',
    'administer notify',
  );
}

/**
 * Implementation of hook_menu().
 */
function notify_menu($may_cache) {
  global $user;
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => "user/{$user->uid}/notify",
      'title' => t('My notification settings'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'notify_user_settings_form',
        $user->uid,
      ),
      'access' => user_access('access notify'),
      'type' => MENU_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'admin/user/user/notify',
      'title' => t('Notifications'),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'notify_admin_users',
      'access' => user_access('administer notify'),
      'type' => MENU_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'admin/content/notify',
      'title' => t('Notification settings'),
      'description' => t('Adjust settings for new content notifications sent by e-mail.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'notify_admin_settings',
      'access' => user_access('administer notify'),
      'type' => MENU_NORMAL_ITEM,
    );
  }
  else {
    if (arg(0) == 'user' && is_numeric(arg(1)) && arg(1) != $user->uid) {
      $account = user_load(array(
        'uid' => arg(1),
      ));
      if ($account->uid) {
        $items[] = array(
          'path' => 'user/' . $account->uid . '/notify',
          'title' => t('Notification settings'),
          'callback' => 'drupal_get_form',
          'callback arguments' => array(
            'notify_user_settings_form',
            arg(1),
          ),
          'access' => user_access('administer notify'),
          'type' => MENU_LOCAL_TASK,
        );
      }
    }
  }
  return $items;
}

/**
 * Menu callback; show user notification options.
 */
function notify_user_settings_form($uid = 0) {
  global $user;
  if ($user->uid != $uid && !user_access('administer notify')) {
    drupal_access_denied();
    return;
  }
  $account = user_load(array(
    'uid' => $uid,
  ));
  if (!is_object($account)) {
    drupal_not_found();
    return;
  }
  $result = db_query('SELECT u.uid, u.name, u.mail, n.status, n.node, n.teasers, n.comment FROM {users} u LEFT JOIN {notify} n ON u.uid = n.uid WHERE u.uid = %d AND u.status = 1', $account->uid);
  $notify = db_fetch_object($result);
  $form = array();
  if (!$notify->mail) {
    drupal_set_message(t('Your e-mail address must be specified on your <a href="@url">my account</a> page.', array(
      '@url' => url('user/' . $account->uid . '/edit'),
    )), 'error');
  }
  $form['notify_page_master'] = array(
    '#type' => 'fieldset',
    '#title' => t('Master switch'),
  );
  $form['notify_page_master']['status'] = array(
    '#type' => 'radios',
    '#title' => t('Notify status'),
    '#default_value' => $notify->status,
    '#options' => array(
      t('Disabled'),
      t('Enabled'),
    ),
    '#description' => t('Do you wish to receive periodic e-mails when new content is posted?'),
  );
  $form['notify_page_detailed'] = array(
    '#type' => 'fieldset',
    '#title' => t('Detailed settings'),
  );
  $form['notify_page_detailed']['node'] = array(
    '#type' => 'radios',
    '#title' => t('Notify new content'),
    '#default_value' => $notify->node,
    '#options' => array(
      t('Disabled'),
      t('Enabled'),
    ),
    '#description' => t('Include new content in the notification mail.'),
  );
  $form['notify_page_detailed']['teasers'] = array(
    '#type' => 'radios',
    '#title' => t('Content'),
    '#default_value' => $notify->teasers,
    '#options' => array(
      t('Title only'),
      t('Title + Teaser'),
      t('Title + Body'),
    ),
    '#description' => t('Select the amount of each post that you would like to see in your notification e-mails.'),
  );
  $form['notify_page_detailed']['comment'] = array(
    '#type' => 'radios',
    '#title' => t('Notify new comments'),
    '#default_value' => $notify->comment,
    '#options' => array(
      t('Disabled'),
      t('Enabled'),
    ),
    '#description' => t('Include new comments in the notification mail.'),
  );
  $form['uid'] = array(
    '#type' => 'value',
    '#value' => $account->uid,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save settings'),
  );
  return $form;
}
function notify_user_settings_form_submit($form_id, $form_values) {
  db_query('DELETE FROM {notify} WHERE uid = %d', $form_values['uid']);
  db_query('INSERT INTO {notify} (uid, status, node, teasers, comment) VALUES (%d, %d, %d, %d, %d)', $form_values['uid'], $form_values['status'], $form_values['node'], $form_values['teasers'], $form_values['comment']);
  drupal_set_message(t('Notify settings saved.'));
}

/**
 * Menu callback; show admininster user notification settings form.
 */
function notify_admin_users() {
  $form = array();
  $form['#tree'] = TRUE;
  $form['info'] = array(
    '#value' => t('The following table shows all users that have notifications enabled.'),
  );
  $form['users'] = array();
  $result = db_query('SELECT u.uid, u.name, u.mail, n.* FROM {users} u LEFT JOIN {notify} n ON u.uid = n.uid WHERE n.status = 1 AND u.status = 1 ORDER BY u.name');
  while ($notify = db_fetch_object($result)) {
    $form['users'][$notify->uid] = array();
    $form['users'][$notify->uid]['name'] = array(
      '#type' => 'markup',
      '#value' => theme('username', $notify),
    );
    $form['users'][$notify->uid]['mail'] = array(
      '#type' => 'markup',
      '#value' => $notify->mail,
    );
    $form['users'][$notify->uid]['node'] = array(
      '#type' => 'checkbox',
      '#default_value' => $notify->node,
    );
    $form['users'][$notify->uid]['teasers'] = array(
      '#type' => 'select',
      '#default_value' => $notify->teasers,
      '#options' => array(
        t('Title only'),
        t('Title + Teaser'),
        t('Title + Body'),
      ),
    );
    $form['users'][$notify->uid]['comment'] = array(
      '#type' => 'checkbox',
      '#default_value' => $notify->comment,
    );
    $form['users'][$notify->uid]['attempts'] = array(
      '#type' => 'markup',
      '#value' => $notify->attempts ? intval($notify->attempts) : 0,
    );
  }
  $form['flush'] = array(
    '#title' => t('Flush e-mail queue'),
    '#type' => 'checkbox',
    '#default_value' => FALSE,
    '#description' => t('Send out any pending notification e-mails currently in queue.'),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save settings'),
  );
  return $form;
}

/**
 * Submit for the notify_admin form.
 */
function notify_admin_users_submit($form_id, $form_values) {
  if ($form_values['users']) {
    foreach ($form_values['users'] as $uid => $settings) {
      db_query('UPDATE {notify} SET node = %d, teasers = %d, comment = %d WHERE uid = %d', $settings['node'], $settings['teasers'], $settings['comment'], $uid);
    }
  }
  drupal_set_message(t('Notify settings saved.'));
  if ($form_values['flush']) {
    list($num_sent, $num_failed) = _notify_send();
    variable_set('notify_send_last', time());
    if ($num_sent > 0) {
      drupal_set_message(t('!count pending notification e-mails have been sent.', array(
        '!count' => $num_sent,
      )));
    }
    elseif ($num_failed > 0) {
      drupal_set_message(t('!count notification e-mails could not be sent.', array(
        '!count' => $num_failed,
      )), 'error');
    }
    else {
      drupal_set_message(t('No notification e-mails needed to be sent.'));
    }
  }
}

/**
 * Theme function to theme the admin user settings form in a table format.
 */
function theme_notify_admin_users($form) {
  $output = drupal_render($form['info']);
  $header = array(
    t('Username'),
    t('E-mail address'),
    t('Content'),
    t('Teasers'),
    t('Comment'),
    t('Failed attempts'),
  );
  $rows = array();
  foreach (element_children($form['users']) as $uid) {
    $row = array();
    foreach (element_children($form['users'][$uid]) as $entry_key) {
      unset($form['users'][$uid][$entry_key]['#title']);
      $row[] = drupal_render($form['users'][$uid][$entry_key]);
    }
    $rows[] = $row;
  }
  if (!$rows) {
    $rows[] = array(
      array(
        'data' => t('No users have notifications enabled.'),
        'colspan' => 6,
      ),
    );
  }
  $output .= theme('table', $header, $rows);
  $output .= drupal_render($form);
  return $output;
}

/**
 * Formatting of outgoing mail, taken from mail.inc, part of project.module
 */
function _notify_content($node, $notify) {
  static $i = 0;
  switch ($notify->teasers) {
    case 0:
      return;
    case 1:
      $txt = check_markup($node->teaser, $node->format, FALSE);
      break;
    case 2:
      $txt = check_markup($node->body, $node->format, FALSE);
  }
  $pattern = '@(<a href="(.+?)">(.+?)</a>)@ei';
  $txt = preg_replace($pattern, "'\\3 ['. _notify_mail_urls('\\2') .']'", $txt);
  $urls = _notify_mail_urls();
  if (count($urls)) {
    $txt .= "\n";
    for ($max = count($urls); $i < $max; $i++) {
      $txt .= '[' . ($i + 1) . '] ' . $urls[$i] . "\n";
    }
  }
  $txt = strip_tags($txt);
  $txt = notify_entities_to_utf8($txt);
  return wordwrap($txt, 72);
}

/**
 * Format HTML version of outgoing mail
 */
function _notify_content_html($node, $notify) {
  switch ($notify->teasers) {
    case 0:
      return;
    case 1:
      return check_markup($node->teaser, $node->format, FALSE);
      break;
    case 2:
      return check_markup($node->body, $node->format, FALSE);
  }
}

/**
 * Helper function to send the notification email.
 * 
 * TODO: Needs some cleanup and themability.
 */
function _notify_send() {
  $period = variable_get('notify_send_last', time() - variable_get('notify_send', 86400));
  $separator = '------------------------------------------------------------------------------';
  $mini_separator = '---';
  $num_sent = 0;
  $num_failed = 0;
  _notify_switch_user();

  // Store current user
  // Fetch users with notify enabled
  $uresult = db_query('SELECT u.uid, u.name, u.mail, n.status, n.node, n.teasers, n.comment FROM {notify} n INNER JOIN {users} u ON n.uid = u.uid WHERE n.status = 1 AND u.status = 1 AND n.attempts <= %d', variable_get('notify_attempts', 5));
  while ($user = db_fetch_object($uresult)) {

    // Switch current user to this account to use node_access functions, etc.
    _notify_switch_user($user->uid);

    // Fetch all new nodes and 'load' it to get proper body, etc.
    $nresult = db_query(db_rewrite_sql('SELECT n.nid FROM {node} n WHERE (n.status = 1 OR n.moderate = 1) AND n.created > %d AND n.created <= %d ORDER BY n.created'), $period, time());
    $nodes = array();
    while ($node = db_fetch_object($nresult)) {
      $nodes[$node->nid] = node_load($node->nid);
    }

    // Fetch new comments.
    $cresult = db_query(db_rewrite_sql('SELECT c.nid, c.cid, c.subject, c.name FROM {comments} c WHERE c.status = %d AND c.timestamp > %d AND c.timestamp <= %d ORDER BY c.nid, c.timestamp', 'c'), COMMENT_PUBLISHED, $period, time());
    $comments = array();
    while ($comment = db_fetch_object($cresult)) {
      $comments[$comment->nid][] = $comment;
    }
    $node_body = '';
    $comment_body = '';

    // Write new node content to e-mail if user has permissions and nodes are
    // ready to be sent.
    if ($user->node && user_access('access content') && count($nodes)) {
      $node_count = 0;
      foreach ($nodes as $node) {

        // Skip to next if this user is NOT allowed to view this node.
        if (!node_access('view', $node)) {
          continue;
        }

        // TODO: Add functionality to hook into moderation modules?
        if ($node->status == 1) {
          $status = t('Published');
        }
        elseif ($node->status == 0) {
          $status = t('Unpublished');
        }
        if ($node_count > 0) {
          $node_body .= $mini_separator . "\n\n";
        }
        $node_body .= ++$node_count . '. ' . $node->title . "\n";
        $node_body .= t('!status !type by !author', array(
          '!status' => $status,
          '!type' => node_get_types('name', $node),
          '!author' => $node->name ? $node->name : variable_get('anonymous', 'Anonymous'),
        )) . "\n";
        $node_body .= '[ ' . url('node/' . $node->nid, NULL, NULL, TRUE) . " ]\n\n";
        $node_body .= _notify_content($node, $user) . "\n";
      }

      // Prepend node e-mail header as long as user could access at least one node.
      if ($node_count > 0) {
        $node_body = $separator . "\n" . t('Recent content - !count', array(
          '!count' => format_plural(count($nodes), '1 new post', '!count new posts'),
        )) . "\n" . $separator . "\n\n" . $node_body;
      }
    }

    // Write new comments to e-mail if user has permissions and there are
    // comments to be sent.
    if ($user->comment && user_access('access comments') && count($comments)) {
      $total_comment_count = 0;
      foreach ($comments as $nid => $comment) {

        // If we don't already have the node, fetch it.
        if (!isset($nodes[$nid])) {
          $nodes[$nid] = node_load($nid);
        }

        // Don't show comments if we're not allowed to view this node.
        if (!node_access('view', $nodes[$nid], $user->uid)) {
          continue;
        }
        if ($comment_body) {
          $comment_body .= $mini_separator . "\n\n";
        }
        $comment_body .= t('!count attached to !type posted by !author: !title', array(
          '!count' => format_plural(count($comment), '1 new comment', '!count new comments'),
          '!title' => $nodes[$nid]->title,
          '!type' => node_get_types('name', $nodes[$nid]),
          '!author' => $nodes[$nid]->name ? $nodes[$nid]->name : variable_get('anonymous', 'Anonymous'),
        )) . "\n";
        $comment_count = 0;
        foreach ($comment as $c) {
          $comment_body .= '   ' . ++$comment_count . '. ' . t('!title by !author', array(
            '!title' => $c->subject,
            '!author' => $c->name ? $c->name : variable_get(anonymous, 'Anonymous'),
          )) . "\n" . '     ' . url('node/' . $nid, NULL, 'comment-' . $c->cid, TRUE) . "\n\n";
          $total_comment_count++;
        }
      }
      if ($total_comment_count > 0) {
        $comment_body = $separator . "\n" . t('Recent comments - !count', array(
          '!count' => format_plural($total_comment_count, '1 new comment', '!count new comments'),
        )) . "\n" . $separator . "\n\n" . $comment_body;
      }
    }
    $body = $node_body . $comment_body;

    // If there was anything new, send mail.
    if ($body) {

      // Set up initial values for e-mail.
      $from = variable_get('site_mail', ini_get('sendmail_from'));
      $from_name = variable_get('site_name', 'Drupal');
      $subject = t('!sitename new content notification for !username', array(
        '!username' => $user->name,
        '!sitename' => variable_get('site_name', 'Drupal'),
      ));
      $body = t('Greetings !user,', array(
        '!user' => $user->name,
      )) . "\n\n" . $body;
      $body .= "\n-- \n";
      $body .= t('This is an automatic e-mail from !sitename.', array(
        '!sitename' => variable_get('site_name', 'Drupal'),
      )) . "\n";
      $body .= t('To stop receiving these e-mails, change your notification preferences at !notify-url', array(
        '!notify-url' => url("user/{$user->uid}/notify", NULL, NULL, TRUE),
      )) . "\n";
      $headers = array();

      //'From' => "$from_name <$from>");
      if (!drupal_mail('notify_mail', $user->mail, $subject, wordwrap($body, 72), $from, $headers)) {
        $num_failed++;
        db_query('UPDATE {notify} SET attempts = attempts + 1 WHERE uid = %d', $user->uid);
        watchdog('error', t('Notify: User %name (%mail) could not be notified. Mail error.', array(
          '%name' => $user->name,
          '%mail' => $user->mail,
        )));
      }
      else {
        $num_sent++;
        watchdog('user', t('Notify: User %name (%mail) notified successfully.', array(
          '%name' => $user->name,
          '%mail' => $user->mail,
        )));
      }
    }
  }

  // Restore user.
  _notify_switch_user();
  return array(
    $num_sent,
    $num_failed,
  );
}
function notify_entities_to_utf8($text) {
  static $table;

  // We store named entities in a table for quick processing.
  if (!isset($table)) {

    // Get all named HTML entities.
    $table = array_flip(get_html_translation_table(HTML_ENTITIES));

    // PHP gives us Windows-1252/ISO-8859-1 data, we need UTF-8.
    $table = array_map('utf8_encode', $table);
  }
  $text = strtr($text, $table);

  // Any remaining entities are numerical. Use a regexp to replace them.
  return preg_replace('/&#(x?)([A-Za-z0-9]+);/e', '_notify_entity_to_utf8("$1", "$2")', $text);
}
function _notify_entity_to_utf8($hex, $codepoint) {
  if ($hex != '') {
    $codepoint = base_convert($codepoint, 16, 10);
  }
  if ($codepoint < 0x80) {
    return chr($codepoint);
  }
  else {
    if ($codepoint < 0x800) {
      return chr(0xc0 | $codepoint >> 6) . chr(0x80 | $codepoint & 0x3f);
    }
    else {
      if ($codepoint < 0x10000) {
        return chr(0xe0 | $codepoint >> 12) . chr(0x80 | $codepoint >> 6 & 0x3f) . chr(0x80 | $codepoint & 0x3f);
      }
      else {
        if ($codepoint < 0x200000) {
          return chr(0xf0 | $codepoint >> 18) . chr(0x80 | $codepoint >> 12 & 0x3f) . chr(0x80 | $codepoint >> 6 & 0x3f) . chr(0x80 | $codepoint & 0x3f);
        }
      }
    }
  }
}
function _notify_mail_urls($url = 0) {
  static $urls = array();
  if ($url) {
    if (strpos($url, '://')) {
      $urls[] = $url;
    }
    elseif (strpos($url, '/') == 0) {
      $urls[] = url(substr($url, 1), NULL, NULL, TRUE);
    }
    else {
      $urls[] = url($url, NULL, NULL, TRUE);
    }
    return count($urls);
  }
  return $urls;
}

/**
 * Switch from original user to mail submision user and back.
 *
 * NOTE: Copied from mailhandler
 *
 * Note: You first need to run _notify_switch_user without
 * argument to store the current user. Call _notify_switch_user
 * without argument to set the user back to the original user.
 *
 * @param $uid The user ID to switch to
 */
function _notify_switch_user($uid = NULL) {
  global $user;
  static $orig_user = array();
  if (isset($uid)) {

    // Should a user visit cron.php, or should this module be invoked via
    // poormanscron and _notify_send() does not complete,
    // the visitor will end up logged in as the "switched to user" for
    // subsequent requests unless we disable saving the session
    // until we are sure we're the invoking user again.
    session_save_session(FALSE);
    $user = user_load(array(
      'uid' => $uid,
    ));
  }
  else {
    if (count($orig_user)) {
      $user = array_shift($orig_user);
      array_unshift($orig_user, $user);
      session_save_session(TRUE);
    }
    else {
      $orig_user[] = $user;
    }
  }
}

Functions

Namesort descending Description
notify_admin_settings Menu callback; display notify settings page.
notify_admin_users Menu callback; show admininster user notification settings form.
notify_admin_users_submit Submit for the notify_admin form.
notify_cron Implementation of hook_cron().
notify_entities_to_utf8
notify_help Implementation of hook_help().
notify_menu Implementation of hook_menu().
notify_perm Implementation of hook_perm().
notify_user Implementation of hook_user().
notify_user_settings_form Menu callback; show user notification options.
notify_user_settings_form_submit
theme_notify_admin_users Theme function to theme the admin user settings form in a table format.
_notify_content Formatting of outgoing mail, taken from mail.inc, part of project.module
_notify_content_html Format HTML version of outgoing mail
_notify_entity_to_utf8
_notify_mail_urls
_notify_send Helper function to send the notification email.
_notify_switch_user Switch from original user to mail submision user and back.