You are here

subscriptions.module in Subscriptions 5

File

subscriptions.module
View source
<?php

/*
 * @ TODO allow admin to edit user subscriptions (http://drupal.org/node/80323)
 * @ TODO make sure content restrictions are being restricted (http://drupal.org/node/13502)
 * @ TODO switch cron send to load actual nodes instead of serialized content
 * @ TODO allow vocabulary subscription
 * @ TODO encapsulate serialization on the database calls so that the functions are passing serialized data
 * @ TODO see http://drupal.org/node/117891 for issues with cron
 */

/*
 * Latest Release notes:
 *  - modified permissions slightly, you will need to update your users permissions to match the new naming scheme
 *
 */
define('SUBSCRIPTIONS_DEFAULT_SUBJECT', t('[@site] @type subscription update for @name : @subject'));
define('SUBSCRIPTIONS_DEFAULT_BODY', t("Greetings, @name.\n\nA @type to which you have subscribed has been updated.\n@title\n@teaser\nTo view the thread, navigate to !url\n\n--\nThis is an automatic message from @site.\nTo manage your subscriptions, browse to !manage-url"));

/**
 * Implementation of hook_help().
 */
function subscriptions_help($section) {
  switch ($section) {
    case 'admin/help#subscriptions':

      // appears on the admin module help page
      return t('
        <p>This module enables users to subscribe to be notified of changes to threads, categories and content types.
        Once enabled, all nodes will have an additional link that allows the user to subscribe to them.
        Additionally, all users will be given an account option to auto-subscribe to any thread to which they post.
        No configuration is required for this module, although roles must be given permission to
        use it.</p>
        <p>While no configuration is required, administrators are offered a few configurable options:</p>
        <p>"<b>Omitted vocabularies</b>" allows the admin to exclude certain node categories from this list of those
        available for subscription.</p>
        <p>"<b>Omitted content types</b>" allows the admin to exclude certain content types from this list of those
        available for subscription.</p>
        <p>"<b>Notify poster of own posts</b>" sends a notification to a node poster about their own posts. Useful principally during testing. Default is OFF.</p>
        <p>"<b>Use cron for notifications</b>" allows you to postpone subscription
        notifications until the next cron job is run.  Default behavior is to notify all subscribers immediately
        upon content change.  This behavior is probably best for low volume sites, but high volume sites could
        observe appreciable pauses upon node or comment insert, and should probably use the cron option.
        <p>"<b>Display watchdog entries for successful mailings</b>" should also probably be disabled for high volume sites,
        as a large number of mailings could completely fill the log.</p>
        <p>"<b>Test held posts prior to sending</b>" tells Subscriptions to test if a node or comment
        is still active\\published prior toi sending a notification.  This is mainly to avoid sending
        notifications for for posts that have been deleted.  This will result in a small performance
        hit, and only makes sense if you are delaying the notifications with "Use cron for notifications".</p>
        <p>"<b>Show Subscriptions users menu on main menu</b>" tells Subscriptions to display the
        Subscriptions user menu, used to manage one\'s own subscriptions, on the main menu.  The default
        setting is OFF.</p>
        <p>"<b>Show Subscriptions users menu under \'my account\'</b>" tells Subscriptions to display the
        Subscriptions user menu, used to manage one\'s own subscriptions, under the \'My Account\' menu.  The default
        setting is ON.</p>
        <p>"<b>Set all users to \'autosubscribe\' by default</b>" set\'s the default value of the \'autosubscribe\'
        option in each user\'s account to ON.  This value will not be set, however, until the user saves their
        account preferences.  This, essentially, pre-checks the option associated with \'autosubscribe\'. The
        default value is OFF.</p>

      ');
  }
}

/**
 * Implementation of hook_perm().
 */
function subscriptions_perm() {
  return array(
    'subscribe to content',
    'subscribe to taxonomy terms',
    'subscribe to content types',
    'subscribe to blogs',
    'admin users subscriptions',
    'maintain own subscriptions',
  );
}

/**
 * Implementation of hook_user().
 */
function subscriptions_user($type, $edit, &$user, $category = NULL) {
  switch ($type) {
    case 'form':
      if ($category == 'account' && (user_access('maintain own subscriptions') || user_access('admin users subscriptions'))) {
        $form['subscriptions'] = array(
          '#type' => 'fieldset',
          '#title' => t('Subscription settings'),
          '#weight' => 5,
          '#collapsible' => TRUE,
        );
        $form['subscriptions']['subscriptions_auto'] = array(
          '#type' => 'checkbox',
          '#title' => t('Autosubscribe'),
          '#default_value' => isset($edit['subscriptions_auto']) ? $edit['subscriptions_auto'] : variable_get('subscriptions_autoset', 0),
          '#description' => t('Checking this box allows you to be automatically subscribed to any thread you create or post a comment to. You will receive an email with a title and link to the post.'),
        );
        $form['subscriptions']['subscriptions_teaser'] = array(
          '#type' => 'checkbox',
          '#title' => t('Include teaser'),
          '#default_value' => isset($edit['subscriptions_teaser']) ? $edit['subscriptions_teaser'] : variable_get('subscriptions_teaser', 0),
          '#description' => t('Checking this box adds an excerpt of the post to the subscription email.'),
        );
        return $form;
      }
      break;
    case 'delete':
      db_query('DELETE FROM {subscriptions} WHERE uid = %d', $user->uid);
      break;
  }
}

/**
 * Admin settings
 */
function subscriptions_settings() {
  if (module_exists('taxonomy')) {
    $form['taxonomy'] = array(
      '#type' => 'fieldset',
      '#title' => t('Taxonomy settings'),
      '#collapsible' => TRUE,
    );
    $vocabularies = taxonomy_get_vocabularies();
    $select[0] = '<' . t('none') . '>';
    foreach ($vocabularies as $vocabulary) {
      $select[$vocabulary->vid] = $vocabulary->name;
    }
    $form['taxonomy']['subscriptions_omitted_taxa'] = array(
      '#type' => 'select',
      '#title' => t('Omitted vocabularies'),
      '#default_value' => variable_get('subscriptions_omitted_taxa', array()),
      '#options' => $select,
      '#description' => t('Select vocabularies which should be <strong>omitted</strong> from subscription listings.'),
      '#multiple' => TRUE,
    );

    // @ TODO write the code that supports this setting

    /*
    $form['taxonomy']['subscriptions_allow_vid'] = array(
      '#type' => 'checkbox',
      '#title' => t('Allow vocabularies subscription'),
      '#default_value' => variable_get('subscriptions_allow_vid', 1),
      '#description' => t('Allow users to subscribe to an entire vocabluary of terms.'),
    );
    */
  }
  $select = array();
  $nodetypes = node_get_types();
  foreach ($nodetypes as $ntype => $nname) {
    $select[$ntype] = $nname->name;
  }
  $form['sub_settings']['subscriptions_omitted_content_types'] = array(
    '#type' => 'select',
    '#title' => t('Omitted content types'),
    '#default_value' => variable_get('subscriptions_omitted_content_types', array()),
    '#options' => $select,
    '#description' => t('Select content types which should be <strong>omitted</strong> from subscription listings.'),
    '#multiple' => TRUE,
  );
  $form['sub_settings']['email'] = array(
    '#type' => 'fieldset',
    '#title' => t('Email settings for subscriptions notification'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#description' => t('Note that themes are able to override these settings.'),
  );
  $form['sub_settings']['email']['subscriptions_email_subject'] = array(
    '#type' => 'textfield',
    '#title' => t('Subject'),
    '#default_value' => variable_get('subscriptions_email_subject', SUBSCRIPTIONS_DEFAULT_SUBJECT),
  );
  $form['sub_settings']['email']['subscriptions_email_body'] = array(
    '#type' => 'textarea',
    '#title' => t('Body'),
    '#default_value' => variable_get('subscriptions_email_body', SUBSCRIPTIONS_DEFAULT_BODY),
    '#description' => t("You may use the following variables: @name (user's name), @type (node type subscribed to), !url (the url of the subscribed node), @site (the name of your site), !manage-url (the url to manage user's subscriptions), @title (the node's title), @teaser (the node's teaser)"),
    '#rows' => 15,
  );
  $form['sub_settings']['subscriptions_sendself'] = array(
    '#type' => 'checkbox',
    '#title' => t('Notify poster of own posts'),
    '#default_value' => variable_get('subscriptions_sendself', 0),
    '#description' => t("Notifies a node poster about their own posts.  Useful principally during testing.  Default is OFF."),
  );
  $form['sub_settings']['subscriptions_usecron'] = array(
    '#type' => 'checkbox',
    '#title' => t('Use cron for notifications'),
    '#default_value' => variable_get('subscriptions_usecron', 0),
    '#description' => t("Sends subscription notification when cron module runs.  Default is to send upon node update.  <br /><em>Note:  Currently only tested with MySQL.</em>"),
  );
  $form['sub_settings']['subscriptions_watchgood'] = array(
    '#type' => 'checkbox',
    '#title' => t('Display watchdog entries for successful mailings'),
    '#default_value' => variable_get('subscriptions_watchgood', 1),
    '#description' => t('Inserts notification of successful mailings in the watchdog log.  Default is ON.'),
  );
  $form['sub_settings']['subscriptions_testpost'] = array(
    '#type' => 'checkbox',
    '#title' => t('Test held posts prior to sending'),
    '#default_value' => variable_get('subscriptions_testpost', 0),
    '#description' => t('Tests to see if a post about to be sent by cron is still active.  Adds a small amount of overhead.  Default is OFF.'),
  );
  $form['sub_settings']['subscriptions_usersmenu'] = array(
    '#type' => 'checkbox',
    '#title' => t('Show Subscriptions users menu under "My account"'),
    '#default_value' => variable_get('subscriptions_usersmenu', 1),
    '#description' => t('Displays the Subscriptions users menu as a tab under "My account". Default is ON.'),
  );
  $form['sub_settings']['subscriptions_autoset'] = array(
    '#type' => 'checkbox',
    '#title' => t('Set all users to "autosubscribe" by default'),
    '#default_value' => variable_get('subscriptions_autoset', 0),
    '#description' => t('Sets each users "autosubscribe" profile option. Default is OFF.'),
  );
  $form['sub_settings']['subscriptions_link_teaser'] = array(
    '#type' => 'checkbox',
    '#title' => t('Show subscribe link with teaser'),
    '#default_value' => variable_get('subscriptions_link_teaser', 1),
    '#description' => t('Uncheck to show link only in node view.'),
  );
  return system_settings_form($form);
}

/**
 * Implementation of hook_menu().
 */
function subscriptions_menu($may_cache) {
  global $user;

  // we need the user to to build some urls
  $items = array();
  $items[] = array(
    'path' => 'admin/settings/subscriptions',
    'title' => t('Subscriptions'),
    'description' => t('Enables site settings for user subscriptions.'),
    'callback' => 'drupal_get_form',
    'callback arguments' => 'subscriptions_settings',
    'access' => user_access('administer site configuration'),
    'type' => MENU_NORMAL_ITEM,
  );
  if (variable_get('subscriptions_usersmenu', 1) && arg(0) == 'user' && is_numeric(arg(1))) {
    $account = user_load(array(
      'uid' => arg(1),
    ));

    // User subscription pages
    if ($user->uid == $account->uid || user_access('admin users subscriptions')) {
      $items[] = array(
        'path' => "user/" . arg(1) . "/subscriptions",
        'title' => t('Subscriptions'),
        'callback' => 'subscriptions_page',
        'access' => user_access('maintain own subscriptions'),
        'type' => MENU_LOCAL_TASK,
        'callback arguments' => array(
          arg(1),
          "content",
        ),
      );

      // User Subscriptions submenus
      if (module_exists('blog')) {
        $items[] = array(
          'path' => "user/" . arg(1) . "/subscriptions/blogs",
          'title' => t('blogs'),
          'callback' => 'subscriptions_page',
          'access' => user_access('subscribe to blogs'),
          'type' => MENU_LOCAL_TASK,
          'callback arguments' => array(
            arg(1),
            "blogs",
          ),
        );
      }

      // comment subscription
      $items[] = array(
        'path' => "user/" . arg(1) . "/subscriptions/node",
        'title' => t('threads'),
        'callback' => 'subscriptions_page',
        'access' => user_access('subscribe to content'),
        'type' => MENU_DEFAULT_LOCAL_TASK,
        'weight' => -1,
        'callback arguments' => array(
          arg(1),
          "content",
        ),
      );

      // Content type subscription
      $items[] = array(
        'path' => "user/" . arg(1) . "/subscriptions/type",
        'title' => t('content types'),
        'callback' => 'subscriptions_page',
        'access' => user_access('subscribe to content types'),
        'type' => MENU_LOCAL_TASK,
        'callback arguments' => array(
          arg(1),
          "type",
        ),
      );

      // Taxonomy subscription
      if (module_exists('taxonomy')) {
        $items[] = array(
          'path' => "user/" . arg(1) . "/subscriptions/taxonomy",
          'title' => t('categories'),
          'callback' => 'subscriptions_page',
          'access' => user_access('subscribe to taxonomy terms'),
          'type' => MENU_LOCAL_TASK,
          'callback arguments' => array(
            arg(1),
            "taxonomy",
          ),
        );
      }

      // RSS feed of subscriptions
      $items[] = array(
        'path' => "user/" . arg(1) . "/subscriptions/feed",
        'title' => t('rss feed'),
        'access' => user_access('maintain own subscriptions'),
        'callback' => 'subscriptions_feed',
        'type' => MENU_LOCAL_TASK,
        'weight' => 1,
        'callback arguments' => array(
          arg(1),
          "feed",
        ),
      );
    }
  }

  // My subscriptions pages
  if ($may_cache) {
    $items[] = array(
      'path' => 'subscriptions',
      'title' => t('My subscriptions'),
      'access' => user_access('subscribe to content'),
      'callback' => 'subscriptions_page',
      'type' => MENU_NORMAL_ITEM,
      'callback arguments' => array(
        $user->uid,
        "content",
      ),
    );

    // comment subscription
    $items[] = array(
      'path' => "subscriptions/node",
      'title' => t('threads'),
      'callback' => 'subscriptions_page',
      'access' => user_access('subscribe to content'),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => -1,
      'callback arguments' => array(
        $user->uid,
        "content",
      ),
    );

    // User Subscriptions submenus
    if (module_exists('blog')) {
      $items[] = array(
        'path' => "subscriptions/blogs",
        'title' => t('blogs'),
        'callback' => 'subscriptions_page',
        'access' => user_access('subscribe to blogs'),
        'type' => MENU_LOCAL_TASK,
        'callback arguments' => array(
          $user->uid,
          "blogs",
        ),
      );
    }

    // Content type subscription
    $items[] = array(
      'path' => "subscriptions/type",
      'title' => t('content types'),
      'callback' => 'subscriptions_page',
      'access' => user_access('subscribe to content types'),
      'type' => MENU_LOCAL_TASK,
      'callback arguments' => array(
        $user->uid,
        "type",
      ),
    );

    // Taxonomy subscription
    if (module_exists('taxonomy')) {
      $items[] = array(
        'path' => "subscriptions/taxonomy",
        'title' => t('categories'),
        'callback' => 'subscriptions_page',
        'access' => user_access('subscribe to taxonomy terms'),
        'type' => MENU_LOCAL_TASK,
        'callback arguments' => array(
          $user->uid,
          "taxonomy",
        ),
      );
    }

    // RSS feed of subscriptions
    $items[] = array(
      'path' => "subscriptions/feed",
      'title' => t('rss feed'),
      'access' => user_access('subscribe to content'),
      'callback' => 'subscriptions_feed',
      'type' => MENU_LOCAL_TASK,
      'weight' => 1,
      'callback arguments' => array(
        $user->uid,
        "feed",
      ),
    );
  }

  /* hiding reporting stub until there is content behind it
    $items[] = array(
      'path' => 'admin/subscriptions',
      'title' => t('subscriptions'),
      'access' => user_access('administer users'),
      'callback' => 'subscriptions_page',
    );
    */
  return $items;
}

/**
 * Returns all subscriptions, and displayed metadata, for a given user.
 * (uses caching)
 */
function subscriptions_get_user($account = NULL) {
  global $user;
  static $subscriptions;
  if (is_null($account)) {
    $account = $user;
  }
  if (is_null($subscriptions[$account->uid])) {

    // query string for node subscriptions
    $queryn = 'SELECT td.tid, td.name, n.nid, n.title, s.stype, s.sid FROM ';
    $queryn .= '(({subscriptions} s LEFT JOIN {node} n ON n.nid = s.sid) ';
    $queryn .= 'LEFT JOIN {term_node} tn ON tn.nid = s.sid) ';
    $queryn .= 'LEFT JOIN {term_data} td ON td.tid = tn.tid ';
    $queryn .= 'WHERE n.status = 1 AND s.uid = %d AND s.stype = \'node\'';

    // query string for blog subscriptions (by blog owner)
    $queryb = 'SELECT u.uid, u.name, s.stype, s.sid FROM ';
    $queryb .= '({subscriptions} s LEFT JOIN {users} u ON u.uid = s.sid) ';
    $queryb .= 'WHERE u.status = 1 AND s.uid = %d AND s.stype = \'blog\'';

    // query string for category subscriptions
    $queryt = 'SELECT td.tid, td.name FROM ';
    $queryt .= '{subscriptions} s INNER JOIN {term_data} td ON td.tid = s.sid ';
    $queryt .= 'WHERE s.uid = %d AND s.stype = \'taxa\'';
    $resultn = db_query($queryn, $account->uid);
    $resultb = db_query($queryb, $account->uid);
    $resultt = db_query($queryt, $account->uid);
    $subscriptions[$account->uid]['node'] = $subscriptions[$account->uid]['taxa'] = $subscriptions[$account->uid]['blog'] = array();
    while ($nsub = db_fetch_object($resultn)) {
      $subscriptions[$account->uid]['node'][$nsub->nid] = $nsub;
    }
    while ($bsub = db_fetch_object($resultb)) {
      $subscriptions[$account->uid]['blog'][$bsub->uid] = $bsub;
    }
    while ($tsub = db_fetch_object($resultt)) {
      $subscriptions[$account->uid]['taxa'][$tsub->tid] = $tsub;
    }
  }
  return $subscriptions[$account->uid];
}

/*
 * Returns a summary of all subscriptions
 */
function subscriptions_get_summary() {

  // query string for node subscriptions
  $queryn = 'SELECT n.nid, n.title, s.sid, COUNT(*) as ncount FROM ';
  $queryn .= '{subscriptions} s INNER JOIN {node} n ON n.nid = s.sid ';
  $queryn .= 'WHERE s.stype = \'node\' ';
  $queryn .= 'GROUP BY n.nid, n.title, s.sid ';
  $queryn .= 'ORDER BY s.sid ';

  // query string for blog subscriptions (by blog owner)
  $queryb = 'SELECT u.uid, u.name, s.sid, COUNT(*) as ncount FROM ';
  $queryb .= '{subscriptions} s INNER JOIN {users} u ON u.uid = s.sid ';
  $queryb .= 'WHERE s.stype = \'blog\' ';
  $queryb .= 'GROUP BY u.uid, u.name, s.sid ';
  $queryb .= 'ORDER BY s.sid ';

  // query string for category subscriptions
  $queryt .= 'SELECT s.sid, td.name, COUNT(*) as ncount FROM ';
  $queryt .= '{subscriptions} s RIGHT JOIN {term_data} td ON td.tid = s.sid ';
  $queryt .= 'WHERE s.stype = \'taxa\' ';
  $queryt .= 'GROUP BY s.sid, td.name ';
  $queryt .= 'ORDER BY s.sid ';

  // query string for content type subscriptions
  $querytp .= 'SELECT s.stype, COUNT(*) as ncount FROM ';
  $querytp .= '{subscriptions} s ';
  $querytp .= 'WHERE s.stype LIKE \'type%\' ';
  $querytp .= 'GROUP BY s.sid ';
  $querytp .= 'ORDER BY s.sid ';
  $resultn = db_query($queryn);
  $resultb = db_query($queryb);
  $resultt = db_query($queryt);
  $resulttp = db_query($querytp);
  $subssumm['node'] = $subssumm['taxa'] = $subssumm['blog'] = $subssumm['type'] = array();
  while ($nsub = db_fetch_object($resultn)) {
    $subssumm['node'][$nsub->nid] = $nsub;
  }
  while ($bsub = db_fetch_object($resultb)) {
    $subssumm['blog'][$bsub->uid] = $bsub;
  }
  while ($tsub = db_fetch_object($resultt)) {
    $subssumm['taxa'][$tsub->tid] = $tsub;
  }
  while ($tpsub = db_fetch_object($resulttp)) {
    $subssumm['type'][$tsub->stype] = $tpsub;
  }
  return $subssumm;
}

/*
 * Takes an incoming mail and sends it via drupal
 *
 * @name
 * @to
 * @subject
 * @body
 * @from
 *
 */
function subscriptions_sendmail($name, $to, $subject, $body, $from, $headers) {
  $mail_success = drupal_mail('subscriptions-sendmail', $to, $subject, $body, $from, $headers);
  if ($mail_success) {
    if (variable_get('subscriptions_watchgood', 1) == 1) {
      watchdog('subscriptions', t('subscription notification for ') . '"' . $name . '" &lt;' . $to . '&gt;');
    }
  }
  else {
    watchdog('subscriptions', t('error mailing subscription notification: ') . '"' . $name . '"  &lt;' . $to . '&gt;', WATCHDOG_ERROR);
  }
}

/*
 * Get e-mail vars
 * This is the main function which generates the outgoing mail
 * @sid
 * @ssid
 * @uid
 * @stype
 * @strsent
 *
 */
function subscriptions_mailvars($sid, $ssid, $uid, $stype, $strsent) {
  global $user, $locale;
  $initial_user = $user;
  $initial_locale = $locale;
  if (function_exists('locale')) {
    $languages = locale_supported_languages();
    $languages = $languages['name'];
  }

  // if comment insertion, get vars
  if ($stype == 'node') {
    $result = db_query('SELECT title FROM {node} WHERE nid = %d', $sid);
    $subject = db_result($result);
    $result = db_query('SELECT u.uid, u.name, u.mail, u.language FROM {users} u INNER JOIN {subscriptions} s ON u.uid = s.uid WHERE u.status= 1 AND s.sid = %d AND s.stype = \'node\'', $sid);
    $strtype = 'thread';
    $nid = $sid;
    $cid = $ssid;
    $page = subscriptions_comment_page($cid, $nid);
    if ($page) {
      $page = "page={$page}";
    }
  }

  // if content type, get vars
  if ($stype == 'type') {
    $typestr = 'type' . $sid;
    $result = db_query('SELECT u.mail, u.name, u.uid, u.language FROM {users} u INNER JOIN {subscriptions} s ON u.uid = s.uid WHERE u.status= 1 AND s.stype =\'' . $typestr . '\'');
    $strtype = 'content type';
    $nid = $ssid;
  }

  // if node insert, test if node has a taxonomy else skip
  if ($stype == 'taxa' && !is_null($sid)) {
    $result = db_query('SELECT name FROM {term_data} WHERE tid = %d', $sid);
    $subject = db_result($result);
    $result = db_query('SELECT u.mail, u.name, u.uid, u.language FROM {users} u INNER JOIN {subscriptions} s ON u.uid = s.uid WHERE u.status= 1 AND s.sid = %d AND stype = \'taxa\'', $sid);
    $strtype = 'category';
    $nid = $ssid;
  }

  // if blog insert, get vars
  if ($stype == 'blog') {
    $result = db_query('SELECT name FROM {users} WHERE uid = %d', $uid);
    $subject = t('new blog for ') . db_result($result);
    $result = db_query('SELECT u.uid, u.name, u.mail, u.language FROM {users} u INNER JOIN {subscriptions} s ON u.uid = s.uid WHERE u.status= 1 AND s.sid = %d AND s.stype = \'blog\'', $sid);
    $strtype = 'blog';
    $nid = $ssid;
  }
  if (empty($nid)) {
    watchdog('subscriptions', "Unable to process: {$stype}, {$sid}, {$ssid}, {$uid}", WATCHDOG_WARNING);
    return;
  }
  $nobj = node_load($nid);

  // loop through subscribers and call mail function
  while ($subscriptions = db_fetch_object($result)) {
    $subscription_user = user_load(array(
      'uid' => $subscriptions->uid,
    ));

    // determine if posters should be notified of their own posts
    if (variable_get('subscriptions_sendself', 0)) {
      $selftest = TRUE;
    }
    else {
      $selftest = $subscription_user->uid != $uid;
    }

    // determine if target reciever has access to the node
    $user = $subscription_user;
    $nodeaccess = node_access('view', $nobj);
    $user = $initial_user;

    // set teaser variable
    if ($subscription_user->subscriptions_teaser) {
      $teaser = is_null($cid) ? $nobj->teaser : db_result(db_query('SELECT comment FROM {comments} WHERE cid = ' . $cid));
    }
    else {
      $teaser = '';
    }
    if ($selftest && $nodeaccess && !is_null($sid) && strpos($strsent, '!' . $subscriptions->uid . '!') === FALSE) {

      // add this user to "previously notified" string
      $strsent .= $subscriptions->uid . '!';

      // translate the message using the reciever's language
      if (function_exists('locale') && $languages[$subscriptions->language]) {
        $locale = $subscriptions->language;
      }

      // @ TODO update these to use more generic variables
      $from = variable_get('site_mail', ini_get('sendmail_from'));
      $body = theme('subscriptions_mail_item_body', $subscription_user, $from, $strtype, $nobj, $cid, $page, $teaser);
      $headers = theme('subscriptions_mail_item_headers', $subscription_user, $from, $strtype, $nobj, $cid, $page);
      $mail_subject = theme('subscriptions_mail_item_subject', $subscription_user, $from, $strtype, $nobj, $cid, $page, $subject);

      // revert to original locale
      $locale = $initial_locale;
      subscriptions_sendmail($subscriptions->name, $subscriptions->mail, $mail_subject, $body, $from, $headers);
    }
  }
  return $strsent;
}

/**
 * Subscribes users to nodes in which they post, if not already subscribed
 */
function subscriptions_autosubscribe($uid, $nid) {
  global $user;

  // if user has auto subscribe enabled
  if ($user->subscriptions_auto) {

    // check to see if already subscribed
    $result = db_query('SELECT sid FROM {subscriptions} WHERE sid = %d AND stype = \'node\' AND uid = %d', $nid, $uid);
    if (!db_num_rows($result)) {

      // if not, subscribe
      subscriptions_add($nid, $user->uid, 'node');
    }
  }
}

/**
 * handling for held nodes
 */
function subscriptions_heldnodes($heldnode, $poster) {
  $strsent = '!';
  $onode = unserialize($heldnode);
  if ($onode->status) {
    if (!empty($onode->taxonomy)) {
      $omitted_taxa = variable_get('subscriptions_omitted_taxa', array());
      foreach ($onode->taxonomy as $vid => $taxa) {
        if ($vid != 'tags' && !in_array($vid, $omitted_taxa)) {
          if (!is_array($taxa)) {
            $taxa = array(
              $taxa,
            );
          }

          // send taxonomy subscriptions
          foreach ($taxa as $tid) {
            $strsent .= subscriptions_mailvars($tid, $onode->nid, $poster, 'taxa', $strsent);
          }
        }
      }
    }
    $strsent .= subscriptions_mailvars($onode->nid, 0, $poster, 'node', $strsent);
    if ($node->type == 'blog') {
      $strsent .= subscriptions_mailvars($onode->uid, $onode->nid, $poster, 'blog', $strsent);
    }

    // @ TODO this is supposed to handle content type sending
    //        does there need to be an if statment here?
    $strsent .= subscriptions_mailvars($onode->type, $onode->nid, $poster, 'type', $strsent);
  }
}

/**
 * handling for held comments
 */
function subscriptions_heldcomments($heldcomment, $poster) {
  $strsent = '!';
  subscriptions_mailvars($heldcomment['nid'], $heldcomment['cid'], $poster, 'node', $strsent);

  // @ TODO taxonomy/type subscribers (missing here, sent in subscriptions_comment for no-cron)
}

/**
 * store node changes for later handling
 */
function subscriptions_hold($content, $ptype, $op, $pid) {
  $strqry = 'INSERT INTO {subscriptions_holding} ( content, ptype, op, pid )  VALUES (\'%s\', \'%s\', \'%s\', %d)';
  db_query($strqry, serialize($content), $ptype, $op, $pid);
}

/**
 * Test to see if a post is still active before notifications are sent
 */
function subscriptions_testpost($content, $ptype) {
  $content = unserialize($content);
  $valid = FALSE;
  switch ($ptype) {
    case 'comment':

      // comment handling
      $cid = is_null($content->cid) ? $content['cid'] : $content->cid;
      $result = db_query('SELECT pid, status FROM {comments} WHERE cid = %d', $cid);
      $row = db_fetch_object($result);
      if (!is_null($row->pid) && $row->pid != 0 && $row->status != 1) {
        $valid = TRUE;
      }
      break;
    case 'node':

      // node handling
      $nid = is_null($content->nid) ? $content['nid'] : $content->nid;
      $result = db_query('SELECT status FROM {node} WHERE nid = %d', $nid);
      if (db_result($result) == 1) {
        $valid = TRUE;
      }
      break;
  }
  return $valid;
}

/**
 * Implementation of cron job.
 */
function subscriptions_cron() {
  if (variable_get('subscriptions_usecron', 0)) {

    // get all currently held node updates
    $result = db_query('SELECT * FROM {subscriptions_holding}');
    while ($row = db_fetch_object($result)) {
      $proceed = TRUE;
      if (variable_get('subscriptions_testpost', 0)) {
        $proceed = subscriptions_testpost($row->content, $row->ptype);
      }
      if ($proceed) {

        // do send
        if ($row->ptype == 'comment') {
          $content = unserialize($row->content);

          // we check the comment status and don't send or delete it
          // if it hasn't been approved
          $comment = db_fetch_array(db_query("SELECT * FROM comments WHERE cid = %d", $content['cid']));
          if ($comment['status'] != 1) {
            subscriptions_heldcomments($content, $row->pid);

            // delete processed row
            db_query('DELETE FROM {subscriptions_holding} WHERE rid = %d', $row->rid);
          }
        }

        // row type == 'comment'
        if ($row->ptype == 'node') {
          subscriptions_heldnodes($row->content, $row->pid);

          // delete processed row
          db_query('DELETE FROM {subscriptions_holding} WHERE rid = %d', $row->rid);
        }

        // row type == 'node'
      }
      else {

        // do delete
        db_query('DELETE FROM {subscriptions_holding} WHERE rid = %d', $row->rid);
      }
    }

    // for each row in results set
  }

  // if using cron to send notifications
}

/**
 * given a comment, return an array of associated taxonomies
 */
function subscriptions_comment_taxa($comment) {
  $nid = is_null($comment->nid) ? $comment['nid'] : $comment->nid;
  $result = db_query('SELECT tid FROM {term_node} WHERE nid = %d', $nid);
  while ($row = db_fetch_object($result)) {
    $taxa[] = $row->tid;
  }
  return $taxa ? $taxa : array();
}

/**
 * Implementation of hook_comment().
 */
function subscriptions_comment($comment, $op) {
  global $user;
  $strsent = '!';

  // $comment can be an object or an array.
  $comment = (array) $comment;
  if ($op == 'insert' || $op == 'update' && $comment['status'] == 0) {

    // ignore deactivated comments
    // if use_cron is set, insert node actions into holding table
    if (variable_get('subscriptions_usecron', 0)) {
      subscriptions_hold($comment, 'comment', $op, $user->uid);
    }
    else {

      // if cron is not used
      $nid = $comment['nid'];
      $nobj = node_load($nid);

      // send node subscriptions
      $strsent .= subscriptions_mailvars($nid, $comment['cid'], $user->uid, 'node', $strsent);

      // get subscription->node->taxonomy
      $taxa = subscriptions_comment_taxa($comment);

      // send to taxonomy subscribers
      foreach ($taxa as $tid) {
        $strsent .= subscriptions_mailvars($tid, $nid, $user->uid, 'taxa', $strsent);
      }

      // send content type subscriptions
      $strsent .= subscriptions_mailvars($nobj->type, $nobj->nid, $user->uid, 'type', $strsent);
    }

    // end cron test
    subscriptions_autosubscribe($user->uid, $nid);
  }
}

/**
* Return the page a comment is on
*/
function subscriptions_comment_page($cid, $nid) {

  // Recipient of email may have different comment settings than the current user.
  // Also recipient may have 'administer comments' permission and see unpublished comments.
  // Just use default settings and hope for the best..

  //$comments_per_page = _comment_get_display_setting('comments_per_page');
  $comments_per_page = variable_get('comment_default_per_page', 50);
  $comments_num = comment_num_all($nid) + 1;

  // +1 for comment being added now
  if ($comments_num <= $comments_per_page) {

    // One page of comments only
    return 0;
  }
  $comment = db_fetch_object(db_query('SELECT timestamp, thread FROM {comments} WHERE cid = %d', $cid));

  // Build the database query that retrieves the comment's position
  // This follows the same scheme as in comment_render().
  // See comments there for an explanation.
  $query = 'SELECT COUNT(*) FROM {comments} WHERE nid = %d AND status = %d';
  $query_args = array(
    $nid,
    COMMENT_PUBLISHED,
  );
  $mode = variable_get('comment_default_mode', COMMENT_MODE_THREADED_EXPANDED);
  $order = variable_get('comment_default_order', COMMENT_ORDER_NEWEST_FIRST);
  if ($order == COMMENT_ORDER_NEWEST_FIRST) {
    if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) {
      $query .= ' AND timestamp > %d';
      $query_args[] = $comment->timestamp;
    }
    else {
      $query .= " AND thread > '%s'";
      $query_args[] = $comment->thread;
    }
  }
  else {
    if ($order == COMMENT_ORDER_OLDEST_FIRST) {
      if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) {
        $query .= ' AND timestamp < %d';
        $query_args[] = $comment->timestamp;
      }
      else {
        $query .= " AND SUBSTRING(thread, 1, (LENGTH(thread) - 1)) < '%s'";
        $query_args[] = substr($comment->thread, 0, -1);
      }
    }
  }
  $count = db_result(db_query($query, $query_args));
  return floor($count / $comments_per_page);
}

/**
 * Implementation of hook_nodeapi().
 */
function subscriptions_nodeapi(&$node, $op, $arg = 0) {
  global $user;
  $strsent = '!';
  switch ($op) {
    case 'update':
      if ($node->status == '0') {

        // unpublished
        break;

        // don't notify
      }

      // prevent already published edits from sending notifications
      // @ TODO send nodes that have been updated could be controlled here
      if ($node->status == '1' && $node->subscriptions_currentstatus == '1') {
        break;
      }

    // else, fall through
    case 'insert':
      if (variable_get('subscriptions_usecron', 0)) {

        // using cron to send notifications
        subscriptions_hold($node, 'node', $op, $user->uid);
      }
      else {

        // sending notification on submission
        if ($node->status) {
          if (!empty($node->taxonomy)) {
            $omitted_taxa = variable_get('subscriptions_omitted_taxa', array());
            foreach ($node->taxonomy as $vid => $taxa) {

              // only have unparsed text for tags at this point, not tid values
              if ($vid != 'tags' && !in_array($vid, $omitted_taxa)) {
                if (!is_array($taxa)) {
                  $taxa = array(
                    $taxa,
                  );
                }

                // send taxonomy subscriptions
                foreach ($taxa as $tid) {
                  $strsent .= subscriptions_mailvars($tid, $node->nid, $user->uid, 'taxa', $strsent);
                }
              }
            }
          }

          // send content type subscriptions
          $strsent .= subscriptions_mailvars($node->type, $node->nid, $user->uid, 'type', $strsent);

          // send node subscriptions
          $strsent .= subscriptions_mailvars($node->nid, 0, $user->uid, 'node', $strsent);
          if ($node->type == 'blog') {

            // send blog subscriptions
            $strsent .= subscriptions_mailvars($node->uid, $node->nid, $user->uid, 'blog', $strsent);
          }
        }
      }

      // cron test
      subscriptions_autosubscribe($user->uid, $node->nid);
      if (isset($node->subscriptions_subscribe)) {
        if ($node->subscriptions_subscribe) {
          subscriptions_add($node->nid, $user->uid, 'node');
        }
        user_save($user, array(
          'subscriptions_subscribe' => $node->subscriptions_subscribe,
        ));
      }
      break;
  }
}

/**
 * Implementation of hook_form_alter().
 */
function subscriptions_form_alter($form_id, &$form) {
  global $user;
  $node = $form['#node'];
  if ($user->uid && !$user->subscriptions_auto && isset($form['type']) && $form['type']['#value'] . '_node_form' == $form_id && $form['#node']->comment == COMMENT_NODE_READ_WRITE) {
    $form['subscriptions'] = array(
      '#type' => 'fieldset',
      '#title' => t('Subscriptions'),
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
      '#weight' => 1,
    );
    $allsubs = subscriptions_get_user();
    $val = isset($node->subscriptions_subscribe) ? $node->subscriptions_subscribe : $allsubs['node'][$node->nid] ? 1 : $user->subscriptions_subscribe;
    $form['subscriptions']['subscriptions_subscribe'] = array(
      '#type' => 'checkbox',
      '#title' => t('Subscribe'),
      '#description' => t('Receive notification of replies or comments to this node.'),
      '#default_value' => $val,
    );
  }
  if (isset($node->status)) {
    $form['subscriptions']['subscriptions_currentstatus'] = array(
      '#type' => 'value',
      '#value' => $node->status,
    );
  }
}

/**
 * Implementation of hook_link().
 */
function subscriptions_link($type, $node = NULL, $teaser = NULL) {
  $omittypes = variable_get('subscriptions_omitted_content_types', array());
  $omittaxa = variable_get('subscriptions_omitted_taxa', array());
  $taxaclear = TRUE;
  $links = array();
  if ($type != 'node' || $node->comment != 2 || !user_access('maintain own subscriptions') || $teaser && !variable_get('subscriptions_link_teaser', 1)) {
    return $links;
  }

  // loop through possibly muliple taxa to determine if any are on omit list
  if (!isset($node->taxonomy)) {
    $node->taxonomy = array();
  }
  foreach ($node->taxonomy as $taxa) {
    if (in_array($taxa->vid, $omittaxa)) {
      $taxaclear = FALSE;
      break;
    }
  }
  if ($taxaclear && !in_array($node->type, $omittypes)) {
    $subscriptions = subscriptions_get_user();
    $name = node_get_types('name', $node);
    if ($node->type == 'blog') {
      if (isset($subscriptions['blog'][$node->uid])) {
        $links['subscriptions_del_blog'] = array(
          'title' => t('Unsubscribe blog'),
          'href' => 'subscriptions/del/blog/' . $node->uid,
          'attributes' => array(
            'title' => t("Stop receiving an e-mail whenever a new entry is made to this person's blog."),
          ),
        );
      }
      else {
        $links['subscriptions_add_blog'] = array(
          'title' => t('Subscribe blog'),
          'href' => 'subscriptions/add/blog/' . $node->uid,
          'attributes' => array(
            'title' => t("Receive an e-mail whenever a new entry is made to this person's blog."),
          ),
        );
      }
    }
    if (isset($subscriptions['node'][$node->nid])) {
      $links['subscriptions_del_node'] = array(
        'title' => t('Unsubscribe post'),
        'href' => 'subscriptions/del/node/' . $node->nid,
        'attributes' => array(
          'title' => t('Stop receiving an e-mail whenever a new comment is posted to this @type.', array(
            '@type' => $name,
          )),
        ),
      );
    }
    else {
      $links['subscriptions_add_node'] = array(
        'title' => t('Subscribe post'),
        'href' => 'subscriptions/add/node/' . $node->nid,
        'attributes' => array(
          'title' => t('Receive an e-mail whenever a comment is posted to this @type.', array(
            '@type' => $name,
          )),
        ),
      );
    }
  }
  return $links;
}

/* *********************************************** */

/* Taxonomy functions */

/* *********************************************** */
function subscriptions_get_taxa($uid) {
  $result = db_query('SELECT sid FROM {subscriptions} WHERE uid = %d and stype=\'taxa\'', $uid);
  while ($taxasub = db_fetch_object($result)) {
    $tsubscriptions[] = $taxasub->sid;
  }
  return $tsubscriptions ? $tsubscriptions : array();
}
function subscriptions_get_types($uid) {
  $result = db_query('SELECT stype FROM {subscriptions} WHERE uid = %d', $uid);
  while ($typesub = db_fetch_object($result)) {
    if (substr($typesub->stype, 0, 4) == 'type') {
      $tsubscriptions[] = substr($typesub->stype, 4);
    }
  }
  return $tsubscriptions ? $tsubscriptions : array();
}
function subscriptions_get_taxa_count() {
  $result = db_query('SELECT sid, count(*) as tcount FROM {subscriptions} WHERE stype=\'taxa\' GROUP BY sid');
  while ($taxasub = db_fetch_object($result)) {
    $tsubscriptions[$taxasub->sid] = $taxasub->tcount;
  }
  return $tsubscriptions ? $tsubscriptions : array();
}
function subscriptions_gen_taxa_links($tid, $taxa) {
  if (in_array($tid, $taxa)) {
    $link = l(t('unsubscribe'), 'subscriptions/del/taxa/' . $tid, array(
      'title' => t('Unsubscribe from this category.'),
    ));
  }
  else {
    $link = l(t('subscribe'), 'subscriptions/add/taxa/' . $tid, array(
      'title' => t('Subscribe to this category.'),
    ));
  }
  return $link;
}
function subscriptions_gen_type_links($type, $types) {
  if (in_array($type, $types)) {
    $link = l(t('unsubscribe'), 'subscriptions/del/type' . $type . '/0', array(
      'title' => t('Unsubscribe from this node type.'),
    ));
  }
  else {
    $link = l(t('subscribe'), 'subscriptions/add/type' . $type . '/0', array(
      'title' => t('Subscribe to this node type.'),
    ));
  }
  return $link;
}
function subscriptions_add($sid, $uid, $stype) {
  db_query('INSERT INTO {subscriptions} ( sid , uid, stype )  VALUES (%d , %d, \'%s\')', $sid, $uid, $stype);
}

/* ******************************************************* */

/*  user screens: display, edit functions */

/* ******************************************************* */

/**
 * query to get list of subscibed nodes
 *
 */
function subscriptions_nodes($account = NULL) {
  global $user;
  if (is_null($account)) {
    $account = $user;
  }

  // query string for node subscriptions
  $query = 'SELECT td.tid, td.name, n.nid, n.type, n.title, s.stype, s.sid FROM ';
  $query .= '(({subscriptions} s LEFT JOIN {node} n ON n.nid = s.sid) ';
  $query .= 'LEFT JOIN {term_node} tn ON tn.nid = s.sid) ';
  $query .= 'LEFT JOIN {term_data} td ON td.tid = tn.tid ';
  $query .= 'WHERE n.status = 1 AND s.uid = %d AND s.stype = \'node\' ';
  $query .= 'ORDER BY n.type, n.title';
  $results = db_query($query, $account->uid);
  $data = array();
  while ($sub = db_fetch_object($results)) {
    $data[$sub->nid] = $sub;
  }
  return drupal_get_form('subscriptions_nodes_list_form', $data, $account);
}

/**
 * return node subscriptions form
 */
function subscriptions_nodes_list_form($data, $account) {
  $subsrows['subform'][] = array(
    '#value' => t('You are currently subscribed to the following:'),
  );
  foreach ($data as $nsub) {
    $title = l($nsub->title, 'node/' . $nsub->nid) . ' [' . t($nsub->type) . ']';
    $subsrows['subform']['subs' . $nsub->nid] = array(
      '#type' => 'checkbox',
      '#title' => $title,
      '#default_value' => 1,
    );
  }
  if (empty($data)) {
    $subsrows['subform'] = array(
      '#value' => t('You are not currently subscribed to any active threads'),
    );
  }
  else {
    $subsrows['user'] = array(
      '#type' => 'hidden',
      '#value' => $account->uid,
    );
    $subsrows['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Save'),
    );
  }
  return $subsrows;
}

/**
 * submit the subscriptions_node_list form
 */
function subscriptions_nodes_list_form_submit($form_id, $form_values) {
  if ($form_id == 'subscriptions_nodes_list_form') {
    foreach ($form_values as $n => $v) {
      if (substr($n, 0, 4) == 'subs' && $v == 0) {

        // if value != 1, delete associated subscription row
        db_query('DELETE FROM {subscriptions} WHERE sid = %d AND uid = %d AND stype = \'%s\'', substr($n, 4), $form_values['user'], 'node');
        $deactivated = TRUE;
      }
    }
    $deactivated ? drupal_set_message(t('Your subscription(s) was deactivated.')) : '';
  }
}

/**
 * get list of blog entries, return form
 */
function subscriptions_blogs($account = NULL) {
  global $user;
  if (is_null($account)) {
    $account = $user;
  }

  // query string for blog subscriptions (by blog owner)
  $query = 'SELECT u.uid, u.name, s.stype, s.sid FROM ';
  $query .= '({subscriptions} s LEFT JOIN {users} u ON u.uid = s.sid) ';
  $query .= 'WHERE u.status = 1 AND s.uid = %d AND s.stype = \'blog\'';
  $results = db_query($query, $account->uid);
  $data = array();
  while ($sub = db_fetch_object($results)) {
    $data[$sub->uid] = $sub;
  }
  return drupal_get_form('subscriptions_blogs_form', $data, $account);
}

/**
 * returns blog subscription form
 */
function subscriptions_blogs_form($data, $account) {
  foreach ($data as $bsub) {
    $title = l($bsub->name, 'blog/' . $bsub->uid);
    $subsrows['subform']['subs' . $bsub->uid] = array(
      '#type' => 'checkbox',
      '#title' => $title,
      '#default_value' => 1,
    );
  }
  if (empty($data)) {
    $subsrows['subform'] = array(
      '#value' => t('You are not currently subscribed to any active blogs'),
    );
  }
  else {
    $subsrows['user'] = array(
      '#type' => 'hidden',
      '#value' => $account->uid,
    );
    $subsrows['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Save'),
    );
  }
  return $subsrows;
}

/**
 * save blog subscription preferences
 */
function subscriptions_blogs_form_submit($form_id, $form_values) {
  foreach ($form_values as $n => $v) {
    if (substr($n, 0, 4) == 'subs' && $v == 0) {

      // if value != 1, delete associated subscription row
      db_query('DELETE FROM {subscriptions} WHERE sid = %d AND uid = %d AND stype = \'%s\'', substr($n, 4), $form_values['user'], 'blog');
      $deactivated = TRUE;
    }
  }
  $deactivated ? drupal_set_message(t('Your subscription was deactivated.')) : '';
}

/**
 * Returns a list of taxonomy subscriptions
 */
function subscriptions_taxa($account = NULL) {
  global $user;
  if (is_null($account)) {
    $account = $user;
  }

  // traverse the taxonomy tree
  $vocabularies = function_exists('taxonomy_help') ? taxonomy_get_vocabularies() : array();

  // omit undesired vocabularies from listing
  $omits = variable_get('subscriptions_omitted_taxa', array());
  foreach ($omits as $omit) {
    unset($vocabularies[$omit]);
  }
  return drupal_get_form('subscriptions_taxa_form', $vocabularies, $account);
}

/**
 * Returns the taxonomy subscription form
 */
function subscriptions_taxa_form($vocabularies, $account) {

  // query string for category subscriptions
  $subs = subscriptions_get_taxa($account->uid);
  $subsrows['subform'][] = array(
    '#value' => t('You are currently subscribed to the following:'),
  );
  foreach ($vocabularies as $vocab) {

    // display vocabulary name and group terms together
    $subsrows['subform'][$vocab->vid] = array(
      '#type' => 'fieldset',
      '#title' => $vocab->name,
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
    );

    // @ TODO create mechanism to allow users to
    //        subscribe to all terms under this vocabulary
    $tree = taxonomy_get_tree($vocab->vid);
    foreach ($tree as $term) {
      $defval = 0;
      foreach ($subs as $tid) {
        if ($tid == $term->tid) {
          $defval = 1;
          break;
        }
      }
      $orgstate[] = array(
        $term->tid,
        $account->uid,
        'taxa',
        $defval,
      );
      $title = l($term->name, 'taxonomy/term/' . $term->tid);
      $subsrows['subform'][$vocab->vid]['subs' . $term->tid] = array(
        '#type' => 'checkbox',
        '#title' => str_repeat('&nbsp;&nbsp;', $term->depth) . $title,
        '#default_value' => $defval,
      );
    }
  }
  if (empty($orgstate)) {
    $subsrows['subform'] = array(
      '#value' => t('There are no active categories.'),
    );
  }
  else {
    $subsrows['orgstate'] = array(
      '#type' => 'hidden',
      '#value' => serialize($orgstate),
    );
    $subsrows['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Save'),
    );
  }
  return $subsrows;
}
function subscriptions_taxa_form_submit($form_id, $form_values) {
  $orgstate = unserialize($form_values['orgstate']);
  foreach ($form_values as $n => $v) {
    if (substr($n, 0, 4) == 'subs') {
      $taxid = substr($n, 4);

      // parse taxid out of sub name
      foreach ($orgstate as $orgsub) {

        // if tid matched, and submitted value is different from original value
        if ($taxid == $orgsub[0] && $v != $orgsub[3]) {
          if ($v == 0) {

            // if unchecked
            db_query('DELETE FROM {subscriptions} WHERE sid = %d AND uid = %d AND stype = \'%s\'', $taxid, $orgsub[1], 'taxa');
            drupal_set_message(t('Your subscription was deactivated.'));
          }
          else {

            // if checked
            $strqry = 'INSERT INTO {subscriptions} ( sid, uid , stype )  VALUES (\'%d\', \'%d\', \'%s\')';
            db_query($strqry, $taxid, $orgsub[1], 'taxa');
            drupal_set_message(t('Your subscription was activated.'));
          }
        }
      }
    }
  }
}

// return content type subscriptions form
function subscriptions_type($account = NULL) {
  global $user;
  if (is_null($account)) {
    $account = $user;
  }

  // get list of all subscribed node types
  $types = subscriptions_get_types($account->uid);

  // get list of available node types
  $tree = node_get_types();
  $omits = variable_get('subscriptions_omitted_content_types', array());
  foreach ($omits as $omit) {
    unset($tree[$omit]);
  }
  return drupal_get_form('subscriptions_type_form', $tree, $account, $types);
}

// return content type subscriptions form
function subscriptions_type_form($tree, $account, $types) {
  foreach ($tree as $ntype => $nname) {
    $defval = in_array($ntype, $types) ? 1 : 0;
    $orgstate[] = array(
      0,
      $account->uid,
      'type' . $ntype,
      $defval,
    );
    $subsrows['subform']['substype' . $ntype] = array(
      '#type' => 'checkbox',
      '#title' => $nname->name,
      '#default_value' => $defval,
    );
  }
  if (empty($tree)) {
    $subsrows['subform'] = array(
      '#value' => t('There are no active content types.'),
    );
  }
  else {
    $subsrows['orgstate'] = array(
      '#type' => 'hidden',
      '#value' => serialize($orgstate),
    );
    $subsrows['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Save'),
    );
  }
  return $subsrows;
}
function subscriptions_type_form_submit($form_id, $form_values) {
  $orgstate = unserialize($form_values['orgstate']);
  foreach ($form_values as $n => $v) {
    if (substr($n, 0, 4) == 'subs') {
      $typeid = substr($n, 4);

      // parse taxid out of sub name
      foreach ($orgstate as $orgsub) {

        // if tid matched, and submitted value is different from original value
        if ($typeid == $orgsub[2] && $v != $orgsub[3]) {
          if ($v == 0) {

            // if unchecked
            db_query('DELETE FROM {subscriptions} WHERE sid = %d AND uid = %d AND stype = \'%s\'', 0, $orgsub[1], $typeid);
            drupal_set_message(t('Your subscription was deactivated.'));
          }
          else {

            // if checked
            $strqry = 'INSERT INTO {subscriptions} ( sid, uid , stype )  VALUES (\'%d\', \'%d\', \'%s\')';
            db_query($strqry, 0, $orgsub[1], $typeid);
            drupal_set_message(t('Your subscription was activated.'));
          }
        }
      }
    }
  }
}

/**
 * displays subscribed content data on user and subuscription pages
 * @ TODO clean up all of these parts
 */
function subscriptions_page($uid, $display_type = NULL) {
  $account = user_load(array(
    'uid' => $uid,
  ));
  $subscribed = FALSE;
  if (!arg(2)) {
    $sid = arg(1);
    $nid = $sid;
    $op = arg(0);
  }
  else {
    $op = arg(1);
    $stype = arg(2);
    $sid = arg(3);
    $nid = arg(4);
    if ($stype == 'node') {
      $nid = $sid;
    }
  }

  //  determine return location
  if (is_null($_SERVER['HTTP_REFERER'])) {
    $rtnloc = "node/{$node->nid}";
  }
  else {
    if (variable_get('clean_url', 0) == 1) {

      // clean URLs on
      global $base_url;

      // extract $base_url from $_SERVER['HTTP_REFERER']
      $istart = strlen($base_url) + 1;
      $rtnloc = substr($_SERVER['HTTP_REFERER'], $istart);
    }
    else {

      // split $_SERVER['HTTP_REFERER'] at "q="
      if (strpos($_SERVER['HTTP_REFERER'], 'q=') > 0) {
        $istart = strpos($_SERVER['HTTP_REFERER'], 'q=' + 2);
      }
      else {
        $istart = 0;
      }
      $rtnloc = substr($_SERVER['HTTP_REFERER'], $istart);
      $rtnloc = urldecode($rtnloc);
    }
    $return = $_SERVER['HTTP_REFERER'];
  }

  // end determine return location
  $message = "";
  switch ($op) {

    // inserts a new subscription into the subscriptions_nodes table
    case 'add':
      subscriptions_add($sid, $uid, $stype);
      $message = t('Your subscription was activated.');
      drupal_set_message($message);
      drupal_goto($rtnloc);
      break;

    // removes a subscription from the subscriptions_nodes table
    case 'del':
      db_query('DELETE FROM {subscriptions} WHERE sid = %d AND uid = %d AND stype = \'%s\'', $sid, $uid, $stype);
      $message = t('Your subscription was deactivated.');
      drupal_set_message($message);
      drupal_goto($rtnloc);
      break;

    // Base report for admin functions
    case 'admin':

      // get all subscriptions for all users
      $subscriptions = subscriptions_get_summary();

      // build node rows
      foreach ($subscriptions['node'] as $nsub) {
        $subrowsn[] = array(
          t('thread'),
          l($nsub->title, 'node/' . $nsub->nid),
          $nsub->ncount,
        );
      }

      // build blog rows
      foreach ($subscriptions['blog'] as $bsub) {
        $subrowsb[] = array(
          t('blog'),
          l($bsub->name, 'blog/' . $bsub->uid),
          $bsub->ncount,
        );
      }

      // traverse the taxonomy tree
      $taxa = subscriptions_get_taxa_count();

      // omit undesired vocabularies from listing
      $vocabularies = taxonomy_get_vocabularies();
      $omits = variable_get('subscriptions_omitted_taxa', array());
      foreach ($omits as $omit) {
        unset($vocabularies[$omit]);
      }
      foreach ($vocabularies as $vocab) {
        $tree = taxonomy_get_tree($vocab->vid);
        foreach ($tree as $term) {
          $subrowst[] = array(
            t('category'),
            $vocab->name . ': ' . l($term->name, 'taxonomy/term/' . $term->tid),
            is_null($taxa[$term->tid]) ? '0' : $taxa[$term->tid],
          );
        }
      }

      // build content type rows
      $tree = node_get_types();
      foreach ($tree as $ntype => $nname) {
        $count = 0;
        foreach ($subscriptions['type'] as $tpsub) {
          if (substr($tpsub->stype, 4) == $ntype) {
            $count = $tpsub->ncount;
          }
        }
        $subrowstp[] = array(
          t('content type'),
          l($nname, $ntype),
          $count,
        );
      }

      // concatentate the arrays
      $headers = array(
        t('type'),
        t('title'),
        t('subscribers'),
      );
      $subrows = array_merge((array) $subrowsn, (array) $subrowsb, (array) $subrowst, (array) $subrowstp);

      // assemble output
      if (!$subrows) {
        $message .= t('<p>No threads or categories are currently subscribed.</p>');
      }
      else {
        $message .= theme('table', $headers, $subrows, array(
          'id' => 'subscriptions',
        ));
      }
      drupal_set_title(t('Subscriptions Summary'));
      return $message;
      break;

    // determines the user's subscription status and displays the right option to change it
    default:

      // set output by type
      switch ($display_type) {
        case 'blogs':
          $output = subscriptions_blogs($account);
          break;
        case 'taxonomy':
          $output = subscriptions_taxa($account);
          break;
        case 'content':
          $output = subscriptions_nodes($account);
          break;
        case 'type':
          $output = subscriptions_type($account);
          break;
      }
      $message .= theme('box', '', $output);
      $message .= theme('xml_icon', url("subscriptions/feed"));
      drupal_add_link(array(
        'rel' => 'alternate',
        'type' => 'application/rss+xml',
        'title' => t("!name Subscriptions", array(
          '!name' => $user->name,
        )),
        'href' => url('subscriptions/feed'),
      ));
      return $message;
  }
}

/**
 * generates rss feed for subscriptions
 * @account is $user object
 */
function subscriptions_feed($account = NULL) {
  if (is_null($account)) {
    global $user;
    $account = $user;
  }
  $subs = subscriptions_get_user($account);
  if ($nodes = $subs['blog']) {
    $uids = implode(',', array_keys($nodes));
    $cond[] = "(n.type = 'blog' AND n.uid IN ({$uids}))";
  }
  if ($nodes = $subs['node']) {
    $nids = implode(',', array_keys($nodes));
    $cond[] = "(n.nid IN ({$nids}))";
  }
  if ($taxas = $subs['taxa']) {
    $tids = implode(',', array_keys($taxas));
    $cond[] = "(tn.tid IN ({$tids}))";
  }

  // content types link differently and will be excluded from this list
  $sql = "SELECT n.nid, max( n.created ) AS nc FROM {node} n LEFT JOIN {term_node} tn ON n.nid=tn.nid WHERE n.status=1";
  if ($cond) {
    $sql .= " AND ( " . implode(' OR ', $cond) . " )";
  }
  $sql .= " GROUP BY n.nid ORDER BY nc DESC";
  $result = db_query($sql);

  //$result = db_query_range(db_rewrite_sql($sql), 0, variable_get('feed_default_items', 10));
  $channel['title'] = t("!name Subscriptions", array(
    '!name' => $account->name,
  ));
  $channel['link'] = url("subscriptions/feed", NULL, NULL, TRUE);

  // $channel['description'] = ;
  node_feed($result, $channel);
}

/* *********************************** */

/* VIEWS functions */

/* *********************************** */
function subscriptions_views_tables() {
  $tables['subscriptions'] = array(
    'name' => 'subscriptions',
    'provider' => 'internal',
    'join' => array(
      'left' => array(
        'table' => 'node',
        'field' => 'nid',
      ),
      'right' => array(
        'field' => 'sid',
      ),
    ),
    'filters' => array(
      'sid' => array(
        'field' => 'uid',
        'name' => 'Subscriptions: Subscribed User',
        'operator' => 'views_handler_operator_eqneq',
        'list' => 'views_handler_filter_usercurrent',
        'list-type' => 'select',
        'help' => t('Combine this with "Node: Type" to find nodes of that type that logged in user is subscribed to'),
      ),
    ),
  );
  return $tables;
}

/* ************************************ */

/* THEME FUNCTIONS */

/* ************************************ */

/**
 * themes an outgoing subscriptions mail
 */
function theme_subscriptions_mail_item_body($to_user, $from_addr, $strtype, $node, $cid, $page, $teaser) {
  static $subjects;
  if ($cid && !isset($subjects[$cid])) {
    $subjects[$cid] = db_result(db_query('SELECT subject FROM {comments} WHERE cid = %d', $cid));
  }
  $body = t(variable_get('subscriptions_email_body', SUBSCRIPTIONS_DEFAULT_BODY), array(
    '@name' => $to_user->name,
    '@type' => t($strtype),
    '!url' => url('node/' . $node->nid, $page ? $page : NULL, $cid ? "comment-{$cid}" : NULL, 1),
    '@site' => t(variable_get('site_name', 'drupal')),
    '!manage-url' => variable_get('subscriptions_usersmenu', 1) ? url('user/' . $to_user->uid . '/subscriptions', NULL, NULL, 1) : url('subscriptions', NULL, NULL, 1),
    '@title' => $node->title . ($cid ? "\n\n" . $subjects[$cid] : ''),
    '@teaser' => strip_tags($teaser),
  ));
  return $body;
}

/**
 * theme a header for an email output
 */
function theme_subscriptions_mail_item_headers($to_user, $from_addr, $strtype, $node, $cid, $page) {
  return array();
}

/**
 * theme a subject for an email output
 */
function theme_subscriptions_mail_item_subject($to_user, $from_addr, $strtype, $node, $cid, $page, $subject) {
  $subject = t(variable_get('subscriptions_email_subject', SUBSCRIPTIONS_DEFAULT_SUBJECT), array(
    '@site' => variable_get('site_name', 'drupal'),
    '@type' => t($strtype),
    '@name' => $to_user->name,
    '@subject' => $subject,
  ));
  return $subject;
}

Functions

Namesort descending Description
subscriptions_add
subscriptions_autosubscribe Subscribes users to nodes in which they post, if not already subscribed
subscriptions_blogs get list of blog entries, return form
subscriptions_blogs_form returns blog subscription form
subscriptions_blogs_form_submit save blog subscription preferences
subscriptions_comment Implementation of hook_comment().
subscriptions_comment_page Return the page a comment is on
subscriptions_comment_taxa given a comment, return an array of associated taxonomies
subscriptions_cron Implementation of cron job.
subscriptions_feed generates rss feed for subscriptions @account is $user object
subscriptions_form_alter Implementation of hook_form_alter().
subscriptions_gen_taxa_links
subscriptions_gen_type_links
subscriptions_get_summary
subscriptions_get_taxa
subscriptions_get_taxa_count
subscriptions_get_types
subscriptions_get_user Returns all subscriptions, and displayed metadata, for a given user. (uses caching)
subscriptions_heldcomments handling for held comments
subscriptions_heldnodes handling for held nodes
subscriptions_help Implementation of hook_help().
subscriptions_hold store node changes for later handling
subscriptions_link Implementation of hook_link().
subscriptions_mailvars
subscriptions_menu Implementation of hook_menu().
subscriptions_nodeapi Implementation of hook_nodeapi().
subscriptions_nodes query to get list of subscibed nodes
subscriptions_nodes_list_form return node subscriptions form
subscriptions_nodes_list_form_submit submit the subscriptions_node_list form
subscriptions_page displays subscribed content data on user and subuscription pages @ TODO clean up all of these parts
subscriptions_perm Implementation of hook_perm().
subscriptions_sendmail
subscriptions_settings Admin settings
subscriptions_taxa Returns a list of taxonomy subscriptions
subscriptions_taxa_form Returns the taxonomy subscription form
subscriptions_taxa_form_submit
subscriptions_testpost Test to see if a post is still active before notifications are sent
subscriptions_type
subscriptions_type_form
subscriptions_type_form_submit
subscriptions_user Implementation of hook_user().
subscriptions_views_tables
theme_subscriptions_mail_item_body themes an outgoing subscriptions mail
theme_subscriptions_mail_item_headers theme a header for an email output
theme_subscriptions_mail_item_subject theme a subject for an email output

Constants