<?php
/**
 * @file
 * Watcher module.
 *
 * Watcher Module
 * by Jakob Persson of NodeOne <jakob@nodeone.se>
 * With ideas and feedback from Hans Dekker of Wordsy.com
 *
 * Module allows users to watch nodes and receive notifications when nodes
 * are updated or commented on.
 *
 * Sponsored by
 *   Wordsy - www.wordsy.com
 *   NodeOne - www.nodeone.se
 */

/***************************************************************************************
 * THEME FUNCTIONS
 ***************************************************************************************/

/**
 * Return a themed personal binder page
 *
 * @param $intro_text
 *    The introduction text displayed at the top of the binder page
 * @param $posts_table_header
 *    Header array for the posts table
 * @param $posts_table_body
 *    Rows array for the posts table
 *
 * @return
 *    A themed personal binder page
 */
function theme_watcher_binder($intro_text = null, $posts_table_header, $posts_table_body) {
  $output  = '<div id="watcher_binder">';

  // Intro text
  if ($intro_text) {
    $output .= '  <div id="watcher_binder_intro">';
    $output .= theme('box', null, $intro_text);
    $output .= '  </div>';
  }

  // Table
# $output .= theme('table', $posts_table_header, $posts_table_body);
  $output .= theme('table', $posts_table_header, $posts_table_body, array('id' => 'twatcher'));

  // Pager
  $output .= theme('pager', NULL, 25, 0);

  $output .= '</div>';

  return $output;
}

/**
 * Return a disabled/enabled notification status icon with link for binder used to
 * toggle email notifications
 *
 * Please note that the JavaScript that handles the AJAX request looks for the class
 * attribute so do not change it, rather override it with your own CSS rules if you
 * want to change the appearance of this element.
 *
 * @param $t
 *    An associative array of localized strings
 * @param $type
 *    A string denoting whether the icon should be of type enabled or disabled
 * @param $url
 *    The URL for the link, usually the URL for toggling email notification status
 * @param $query
 *    Query portion of the link
 *
 * @return
 *    A themed icon for toggling email notifications
 */
function theme_watcher_binder_email_icon($t, $type, $url, $query = null) {
  $output = l(
  ( $type == 'enabled' ? $t['binder_notif_text_enabled'] : $t['binder_notif_text_disabled'] ),
  $url,
  array(
      'query' => $query,
      'attributes' => array(
        'class' => "watcher_binder_send_email_status_icon watcher_binder_send_email_status_icon_$type",
        'title' => ( $type == 'enabled' ? $t['binder_notif_title_enabled'] : $t['binder_notif_title_disabled'] ),
        'email_status' => $type,
  ))
  );
  return $output;
}

/**
 * Return a stop watching post icon with link for binder
 *
 * @param $url
 *    A URL for toggling watching of given nid
 * @param $query
 *    Query portion of the link
 *
 * @return
 *    A themed link for stopping watching a node
 */
function theme_watcher_binder_stop_watching_icon($url, $query = null) {
  $output = l(
  t('Stop watching'),
  $url,
  array(
      'query' => $query,
      'attributes' => array(
        'class' => "watcher_binder_stop_watching_icon",
        'title' => t('Stop watching this post.'),
  ))
  );
  return $output;
}

/**
 * Return a stop watching post icon with link to be displayed in nodes
 *
 * Please note that the JavaScript that handles the AJAX request looks for the class
 * attribute so do not change it, rather override it with your own CSS rules if you
 * want to change the appearance of this element.
 *
 * @param $uid
 *    The user we'd like to toggle watching for
 * @param $nid
 *    The node id we're toggling watching for for this user
 * @param $query
 *    Query portion of the link
 * @param $user_is_watching
 *    A boolean denoting whether uid is watching nid
 * @param $t
 *    An associative array of localized strings
 *
 * @return
 *    A themed widget for toggling watching of the given nid
 *
 */
function theme_watcher_node_toggle_watching_link($uid, $nid, $query, $user_is_watching, $t = array()) {

  // Set up classes
  $class_watch_status_link = ( $user_is_watching ? ' watcher_node_toggle_watching_link_watched' : '');
  $class_watch_status_container = ( $user_is_watching ? ' watcher_node_watched' : '');

  $output = '<div class="watcher_node'. $class_watch_status_container .'">';

  // Output a link
  $output .= l(
  ( $user_is_watching ? $t['watch_toggle_enabled'] : $t['watch_toggle_disabled'] ),
    "user/$uid/watcher/toggle/$nid",
  array(
      'query' => $query,
      'attributes' => array(
        'class' => "watcher_node_toggle_watching_link$class_watch_status_link",
        'title' => ( $user_is_watching ? $t['watch_toggle_enabled_title'] : $t['watch_toggle_disabled_title'] ),
  ))
  );

  $output .= '</div>';

  return $output;
}

/**
 * Return the help page
 *
 * @param $content
 *    The content for the help page as configured on the module's settings page
 *
 * @return
 *    A themed help page
 *
 */
function theme_watcher_help_page($content) {
  $output =  '<div id="watcher_help_page">';
  $output .= $content;
  $output .= '</div>';
  return $output;
}

/**
 * Return system help page
 *
 * @param $content
 *    The content for the help page as configured on the module's settings page
 *
 * @return
 *    A themed help page
 *
 */
function theme_watcher_help($content) {
  $output =  '<div id="watcher_help"><pre>';
  $output .= $content;
  $output .= '</pre></div>';
  return $output;
}

/**
 * Return a themed message telling the user the user settings are defaults
 *
 * @param $defaults_text
 *      Localized message
 */
function theme_watcher_settings_defaults_notice($defaults_text) {
  return '<div class="watcher_settings_defaults_notice">'. $defaults_text .'</div>';
}

/**
 * Implementation of hook_theme()
 */
function watcher_theme($existing, $type, $theme, $path) {
  return array(
    'watcher_binder' => array(
      'arguments' => array('intro_text' => null, 'posts_table_header' => null, 'posts_table_body' => null),
  ),
    'watcher_binder_email_icon' => array(
      'arguments' => array('t' => null, 'type' => null, 'url' => null, 'query' => null),
  ),
    'watcher_binder_stop_watching_icon' => array(
      'arguments' => array('url' => null, 'query' => null),
  ),
    'watcher_node_toggle_watching_link' => array(
      'arguments' => array('uid' => null, 'nid' => null, 'query' => null, 'user_is_watching' => null, 't' => array()),
  ),
    'watcher_help_page' => array(
      'arguments' => array('content' => null),
  ),
    'watcher_help' => array(
      'arguments' => array('content' => null),
  ),
    'watcher_settings_defaults_notice' => array(
      'arguments' => array('defaults_text' => null),
  ),
  );
}

/***************************************************************************************
 * HOOK IMPLEMENTATIONS
 ***************************************************************************************/

/**
 * Implementation of hook_perm()
 */
function watcher_perm() {
  return array('administer watcher', 'use watcher', 'change own user settings', 'access help page', 'access others lists of watched posts');
}

/**
 * Implementation of hook_menu().
 */
function watcher_menu() {
  global $user;
  $items = array();

  // Include file
  $base_include = array(
    'file' => 'watcher.db.inc'
    );

    $items['user/%user/watcher'] = array(
    'title' => 'My Watched Posts',
    'page callback' => '_watcher_binder',
    'page arguments' => array(1),
    'access callback' => '_watcher_menu_access_binder',
    'access arguments' => array(1),
#   'type' => MENU_LOCAL_TASK,
    'type' => MENU_NORMAL_ITEM,
    ) + $base_include;

    $items['user/%user/watcher/binder'] = array(
    'title' => 'Watched Posts',
    'page callback' => '_watcher_binder',
    'page arguments' => array(1),
    'access callback' => '_watcher_menu_access_binder',
    'access arguments' => array(1),
    'weight' => 0,
    'type' => MENU_DEFAULT_LOCAL_TASK,
    ) + $base_include;

    $items['user/%user/watcher/settings'] = array(
    'title' => 'Settings',
    'weight' => 5,
    'page callback' => 'drupal_get_form',
    'page arguments' => array('_watcher_user_settings', 1),
    'access arguments' => array('change own user settings'),
    'type' => MENU_IS_LOCAL_TASK
    ) + $base_include;

    $items['user/%/watcher/help'] = array(
    'title' => 'Help',
    'weight' => 10,
    'page callback' => '_watcher_help_page',
    'access arguments' => array('access help page'),
    'type' => MENU_IS_LOCAL_TASK
    ) + $base_include;


    // Callbacks for toggling

    $items['user/%user/watcher/toggle/%node'] = array(
    'title' => 'Watcher Toggle Watching Post',
    'page callback' => '_watcher_watch_toggle',
    'page arguments' => array(4),
    'access callback' => '_watcher_menu_access_toggle_watching_post',
    'access arguments' => array(1),
    'type' => MENU_CALLBACK
    ) + $base_include;

    $items['user/%user/watcher/email_notifications_toggle'] = array(
    'title' => 'Watcher Toggle Email Notifications',
    'page callback' => '_watcher_email_notifications_toggle',
    'access callback' => '_watcher_menu_access_toggle_email_notifications',
    'access arguments' => array(1),
    'type' => MENU_CALLBACK
    ) + $base_include;

    // Unwatching for anonymous users
    //
    // NOTE: Path must not end with /%/%, else callback arguments will be garbled!
    //       Even if we pass additional path args, such as arg(6) and arg(7),
    //       we do not need to wildcard these.

    $items['user/%user/watcher/toggle/%node/unwatch'] = array(
    'page callback' => '_watcher_watch_toggle',
    'page arguments' => array(4, 6, 'node', 7),
    'access callback' => '_watcher_menu_access_toggle_watching_post',
    'access arguments' => array(1),
    'type' => MENU_CALLBACK
    ) + $base_include;

    $items['user/%user/watcher/toggle/%node/unwatch-all'] = array(
    'page callback' => '_watcher_watch_toggle',
    'page arguments' => array(4, 6, 'all', 7),
    'access callback' => '_watcher_menu_access_toggle_watching_post',
    'access arguments' => array(1),
    'type' => MENU_CALLBACK
    ) + $base_include;

    // Administration Panel Settings

    $items['admin/settings/watcher'] = array(
    'title' => 'Watcher',
    'description' => 'Modify the settings for Watcher module.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('_watcher_admin_settings'),
    'access arguments' => array('administer watcher'),
    'type' => MENU_NORMAL_ITEM,
    ) + $base_include;

    $items['admin/settings/watcher/settings'] = array(
    'title' => 'Settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('_watcher_admin_settings'),
    'access arguments' => array('administer watcher'),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    ) + $base_include;

    $items['admin/settings/watcher/stats'] = array(
    'title' => 'Statistics',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('_watcher_admin_stats'),
    'access arguments' => array('administer watcher'),
    'type' => MENU_LOCAL_TASK,
    ) + $base_include;

    $items['admin/settings/watcher/test'] = array(
    'title' => 'Testing',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('_watcher_admin_test'),
    'access arguments' => array('administer watcher'),
    'type' => MENU_LOCAL_TASK,
    ) + $base_include;

    // Binder shortcut, redirect to user's My Watched Posts page
    $items['user/watcher'] = array(
    'title' => 'My Watched Posts',
    'page callback' => '_watcher_binder_shortcut',
    'access callback' => '_watcher_menu_access_binder_shortcut',
    'type' => MENU_NORMAL_ITEM,
    ) + $base_include;

    return $items;
}

/**
 * Access callback to check access to binder (own or others')
 */
function _watcher_menu_access_binder($account) {
  global $user;
  return (
  $user->uid == 1 ||
  ($user->uid == $account->uid && user_access('use watcher')) ||
  (user_access('access others lists of watched posts') && _watcher_user_settings_load('watcher_share_binder', $account->uid))
  );
}

/**
 * Access callback to check access to toggle watching a post
 */
function _watcher_menu_access_toggle_watching_post($account) {
  global $user;
  return (
  $user->uid == 1 ||
  ($user->uid == $account->uid && user_access('use watcher'))
  );
}

/**
 * Access callback to check access to toggle watching a post
 */
function _watcher_menu_access_toggle_email_notifications($account) {
  global $user;
  return (
  $user->uid == 1 ||
  ($user->uid == $account->uid && user_access('use watcher'))
  );
}

/**
 * Access callback to check access to binder shortcut
 * We check to see if current user has a non-zero UID (authenticated)
 */
function _watcher_menu_access_binder_shortcut() {
  global $user;
  return ((bool) $user->uid);
}

/**
 * Implementation of hook_nodeapi()
 */
function watcher_nodeapi(&$node, $op, $a3, $a4) {

  // Check whether this is one of the node types for which Watcher is enabled
  if (!_watcher_node_type_enabled($node)) {
    return false;
  }

  // Include DB dependent functions
  module_load_include('inc', 'watcher', 'watcher.db');

  // Dispatch the operator.
  $function = '_watcher_hook_nodeapi_'. $op;
  if (function_exists($function)) {
    return $function($node, $a3, $a4);
  }
}

/**
 * Implementation of hook_nodeapi(), op: view
 */
function _watcher_hook_nodeapi_view(&$node, $a3, $a4) {
  global $user;
  if (user_access('use watcher')) {
    _watcher_node_watch_link($node, $a3, $a4);
  }
}

/**
 * Implementation of hook_nodeapi(), op: insert
 */
function _watcher_hook_nodeapi_insert(&$node, $a3, $a4) {
  global $user;
  if ($user->uid) {
    _watcher_user_autowatch_node_insert($node->nid);
  }
}

/**
 * Implementation of hook_nodeapi(), op: update
 */
function _watcher_hook_nodeapi_update(&$node, $a3, $a4) {
  _watcher_email_notify_node_update($node);
}

/**
 * Implementation of hook_nodeapi(), op: delete
 */
function _watcher_hook_nodeapi_delete(&$node, $a3, $a4) {
  _watcher_db_delete_nid($node->nid);
}

/**
 * Implementation of hook_comment()
 */
function watcher_comment($comment, $op) {

  // Include DB dependent functions
  module_load_include('inc', 'watcher', 'watcher.db');

  // Dispatch the operator.
  $function = '_watcher_hook_comment_'. $op;
  if (function_exists($function)) {
    return $function($comment);
  }
}

/**
 * Implementation of hook_comment(), op: insert
 */
function _watcher_hook_comment_insert($comment) {
  global $user;

  // Check whether this is one of the node types for which Watcher is enabled
  if (!_watcher_node_type_enabled($comment['nid'])) {
    return false;
  }

  // Only add the node to user's watch list if this is a non-anonymous user
  if ($user->uid) {
    _watcher_user_autowatch_comment_insert($comment);
  }

  // Only send notifications if the comment does not need approval first
  if (user_access('post comments without approval')) {
    //Notify those that watch this node
    _watcher_email_notify_comment_insert($comment);
  }
}

/**
 * Implementation of hook_comment(), op: publish
 *
 * @param $comment A comment object.
 */
function _watcher_hook_comment_publish($comment) {

  // comment_publish is passed a comment as an object so we have to cast
  // it as an array first
  $comment = (array) $comment;

  // Check whether this is one of the node types for which Watcher is enabled
  if (!_watcher_node_type_enabled($comment['nid'])) {
    return false;
  }

  $comment_author = user_load(array('uid' => $comment['uid']));

  // If this comment was unpublished as a result of it requiring approval
  // notify the users watching the node now that it's deemed worthy to publish
  if (!user_access('post comments without approval', $comment_author)) {
    //Notify those that watch this node
    _watcher_email_notify_comment_insert($comment);
  }
}

/**
 * Implementation of hook_comment(), op: update
 *
 * @param $comment An array of comment edit form values.
 */
function _watcher_hook_comment_update($comment) {

  // Check whether this is one of the node types for which Watcher is enabled
  if (!_watcher_node_type_enabled($comment['nid'])) {
    return false;
  }

  // Make sure the comment is published
  if ($comment['status'] == COMMENT_PUBLISHED) {

    // If the comment author is anonymous, the uid field is null
    $comment_author = user_load(array('uid' => (is_null($commment['uid']) ? 0 : $commment['uid'])));

    // If this comment was unpublished as a result of it requiring approval
    // notify the users watching the node now that it's deemed worthy to publish
    if (!user_access('post comments without approval', $comment_author)) {
      //Notify those that watch this node
      _watcher_email_notify_comment_insert($comment);
    }

  }
}


/**
 * Implementation of hook_user()
 */
function watcher_user($type, &$edit, &$account) {

  // Include DB dependent functions
  module_load_include('inc', 'watcher', 'watcher.db');

  // Dispatch the operator.
  $function = '_watcher_hook_user_'. $type;
  if (function_exists($function)) {
    $function($edit, $account);
  }
}

/**
 * Implementation of hook_user(), op: insert
 */
function _watcher_hook_user_insert($edit, $account, $category = NULL) {
  _watcher_user_settings_update_defaults();
}

/**
 * Implementation of hook_user(), op: delete
 */
function _watcher_hook_user_delete($edit, $account, $category = NULL) {
  _watcher_db_delete_user_settings($account->uid);
}

/**
 * Implementation of hook_cron()
 */
function watcher_cron() {

  // Include DB dependent functions
  module_load_include('inc', 'watcher', 'watcher.db');

  // Send emails in queue
  if (_watcher_email_notifications_send_cron()) {
    watchdog('Watcher', 'Notifications were sent on cron.');
  }
}

/**
 * Implementation of hook_mail()
 */
function watcher_mail($key, &$message, $params) {
  $message['subject'] = $params['subject'];
  $message['body'] = $params['body'];
}

/**
 * Implementation of hook_exit()
 */
function watcher_exit() {
  _watcher_email_notifications_send_instant();
}

/***************************************************************************************
 * ADMINISTRATION
 ***************************************************************************************/

/**
 * This is our admin settings page
 */
function _watcher_admin_settings(&$form_state) {

  global $user;

  // Update user settings defaults
  _watcher_user_settings_update_defaults();

  $form = array();

  // Content Type Settings
  _watcher_admin_settings_content_types($form);

  // Watch Toggle Link Settings
  _watcher_admin_settings_watch_toggle_link($form);

  // Personal Binder Settings
  _watcher_admin_settings_personal_binder($form);

  // Email Notification Settings
  _watcher_admin_settings_email_notifications($form);

  // Settings for Default Settings
  _watcher_admin_settings_default_user_settings($form);

  // Help Page Text
  _watcher_admin_settings_help($form);


  return system_settings_form($form);

}

/**
 * Settings concerning content types
 * @param $form
 */
function _watcher_admin_settings_content_types(&$form) {
  $form['contenttypes'] = array(
    '#type' => 'fieldset',
    '#title' => t('Watchable Content Types'),
    '#description' => t('Select the content types that may be watched.'),
    '#collapsible' => true,
    '#collapsed' => true,
  );

  // Display error message if no node types have been selected
  $watchable_node_types = variable_get('watcher_content_types', false);
  if (!$watchable_node_types) {
    drupal_set_message(t("You haven't selected any watchable content types. This module will not work until you do so."), 'error');
  }

  $form['contenttypes']['watcher_content_types'] = array(
      '#type' => 'checkboxes',
      '#default_value' => $watchable_node_types,
      '#options' => node_get_types('names'),
      '#default_value' => variable_get('watcher_content_types', array()),
  );
}

/**
 * Settings concerning the watch toggle link
 * @param $form
 */
function _watcher_admin_settings_watch_toggle_link(&$form) {
  $form['togglelink'] = array(
    '#type' => 'fieldset',
    '#title' => t('Watch Toggle Link'),
    '#collapsible' => true,
    '#collapsed' => true,
  );

  $form['togglelink']['watcher_toggle_link_in_teaser'] = array(
      '#type' => 'checkbox',
      '#title' => t('Display "watch this post" toggle link in teasers.'),
      '#description' => t('Determines whether or not to show the "watch this post" toggle link in node teasers (used on front page and content listings). Teasers are the shortened down version of a node, often shown on a site\'s frontpage or on pages that summarize content. If disabled, link will only be shown when nodes are shown as pages (full view).'),
      '#default_value' => variable_get('watcher_toggle_link_in_teaser', false),
  );
}

/**
 * Settings concerning the personal binder
 * @param $form
 */
function _watcher_admin_settings_personal_binder(&$form) {
  global $user;

  // Load notification message templates
  $templates = _watcher_admin_settings_field_value_templates();

  $form['display'] = array(
    '#type' => 'fieldset',
    '#title' => t('Display Settings for Personal Binder'),
    '#collapsible' => true,
    '#collapsed' => true,
  );

  $form['display'][] = array(
      '#value' => '<p>'. l(t('Click here to view your personal binder'), "user/$user->uid/watcher") .'</p>',
  );

  $form['display']['watcher_display_intro_text'] = array(
      '#type' => 'textarea',
      '#title' => t('Text at the top of the Personal Binder page'),
      '#description' => t('You are recommended to use this space to display <strong>brief</strong> information about what this page does and how it works to the user.'),
      '#default_value' => variable_get('watcher_display_intro_text', $templates['watcher_display_intro_text']),
      '#rows' => 5,
  );

  $form['display']['posts_table'] = array(
      '#type' => 'fieldset',
      '#title' => t('Settings for the table of posts'),
  );

  $form['display']['posts_table']['watcher_display_node_type'] = array(
        '#type' => 'checkbox',
        '#title' => t('Display Node Type Column'),
        '#default_value' => variable_get('watcher_display_node_type', 0),
  );

  $form['display']['posts_table']['watcher_display_last_updated'] = array(
        '#type' => 'checkbox',
        '#title' => t('Display Last Updated Column'),
        '#default_value' => variable_get('watcher_display_last_updated', 0),
  );

  $form['display']['posts_table']['watcher_display_post_author'] = array(
        '#type' => 'checkbox',
        '#title' => t('Display Post Author Column'),
        '#default_value' => variable_get('watcher_display_post_author', 1),
  );
}

/**
 * Settings concerning email notifications
 * @param $form
 */
function _watcher_admin_settings_email_notifications(&$form) {
  // Load notification message templates
  $templates = _watcher_admin_settings_field_value_templates();

  $form['notifications'] = array(
    '#type' => 'fieldset',
    '#title' => t('Settings for Email Notifications'),
    '#collapsible' => true,
    '#collapsed' => true,
  );

  $form['notifications'][] = array(
      '#value' => '<div style="border: 1px solid #0062A0; margin: 1em; padding: 0 0 0 1em; background: #CBE2F1; font-weight: bold;">',
  );

  $form['notifications']['watcher_email_notifications_enabled'] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable Email Notifications'),
      '#default_value' => variable_get('watcher_email_notifications_enabled', 1),
  );

  $form['notifications'][] = array(
      '#value' => '</div>',
  );

  $form['notifications']['watcher_email_notifications_method'] = array(
      '#type' => 'radios',
      '#title' => t('Messages are sent'),
      '#description' => t('You can select when messages are sent. Using "on cron", messages are sent when cron is being run, usually every hour depending on how you have configured crontab, this is preferred for large sites with hundreds of users or more. Using the second method, messages are sent right away for the time limit set (see below). Any messages that remain unsent will be sent next time messages are sent. The second method won\'t affect loading times since messages are sent after the page has been delivered to the user\'s browser.'),
      '#default_value' => variable_get('watcher_email_notifications_method', 'cron'),
      '#options' => array(
        'cron' => t('on cron (recommended for large sites)'),
        'instant' => t('instantaneously and for limited number of seconds (recommended for small to medium sites)')
  ),
  );

  // Snippet taken from simplenews.module
  $max_time = array(1, 2, 3, 4);
  $max_time = drupal_map_assoc($max_time);
  $ini_max = ini_get('max_execution_time');
  if ($ini_max) {
    for ($i = 5; $i < $ini_max; $i=$i+5) {
      $max_time[$i] = $i;
    }
    $max_time['unlimited'] = t('unlimited');
  }

  $form['notifications']['watcher_email_notifications_send_time_limit'] = array(
      '#type' => 'select',
      '#title' => t('Time limit for sending messages in seconds'),
      '#description' => t('This setting lets you specify for how many seconds messages should be sent and it applies to both methods. Any messages that remain unsent after this interval will be sent next time messages are sent. This setting will not make your site seem slower to visitors. The maximum value is the max execution time in your PHP configuration.'),
      '#default_value' => variable_get('watcher_email_notifications_send_time_limit', 5),
      '#options' => $max_time,
      '#attributes' => (array('id' => 'watcher_send_time_limit')),
  );

  $form['notifications']['watcher_email_notifications_comment_excerpt_len'] = array(
      '#type' => 'select',
      '#title' => t('Limit length of comment excerpt in notification email to'),
      '#description' => t('It may be wise not to send the entire comment as doing so will tease the user to go to your site to view it which results in traffic. Here you can select how much of the comment you want to include in the email. Percentages refer to the length of the comment. "Nothing" means no excerpt will be included in the message.'),
      '#default_value' => variable_get('watcher_email_notifications_comment_excerpt_len', '200chars'),
      '#options' => array(
        'nothing' => t('nothing'),
        '25percent' => t('25% of comment'),
        '50percent' => t('50% of comment'),
        '75percent' => t('75% of comment'),
        '100chars' => t('100 characters of comment'),
        '200chars' => t('200 characters of comment'),
        '300chars' => t('300 characters of comment'),
        '400chars' => t('400 characters of comment'),
        'entire' => t('entire comment'),
  )
  );

  $form['notifications']['templates'] = array(
    '#type' => 'fieldset',
    '#title' => t('Notification Message Templates'),
    '#description' => t('These message templates are used for notifications and confirmation messages. You may edit them if you like. Tokens are in [brackets] and act as placeholders for variables, such as URLs, usernames and email addresses, and will be replaced when the email is sent. <strong>You cannot use HTML code in your message templates.</strong>'),
    '#collapsible' => true,
    '#collapsed' => true,
  );

  // Header

  $form['notifications']['templates']['watcher_notifications_templates_header'] = array(
      '#type' => 'textarea',
      '#title' => t('Message Header'),
      '#default_value' => variable_get('watcher_notifications_templates_header', $templates['header']),
      '#description' => t('<p>The text above will be prepended to every message sent. The following tokens are available:</p><ul><li>[recipient-username]</li><li>[recipient-user-settings-url]</li><li>[stop-watching-url]</li></ul>')
  );

  $form['notifications']['templates']['tokens_header'] = array(
      '#type' => 'fieldset',
      '#title' => t('Additional Tokens for Header'),
      '#collapsible' => true,
      '#collapsed' => true,
  );

  $form['notifications']['templates']['tokens_header'][] = array(
        '#value' => theme('token_help', 'global'),
  );

  // Footer

  $form['notifications']['templates']['watcher_notifications_templates_footer'] = array(
      '#type' => 'textarea',
      '#title' => t('Message Footer'),
      '#default_value' => variable_get('watcher_notifications_templates_footer', $templates['footer']),
      '#description' => t('<p>The text above will be appended to every message sent. The following tokens are available:</p><ul><li>[recipient-username]</li><li>[recipient-user-settings-url]</li><li>[stop-watching-url]</li></ul>')
  );

  $form['notifications']['templates']['watcher_notifications_templates_footer_anonymous_notify'] = array(
      '#type' => 'textarea',
      '#title' => t('Message Footer for Anonymous Users'),
      '#default_value' => variable_get('watcher_notifications_templates_footer_anonymous_notify', $templates['footer_anonymous_notify']),
      '#description' => t("<p>The text above will be appended to notification messages messages sent to anonymous users. The following tokens are available:</p><ul><li>[stop-watching-url]</li><li>[stop-watching-url]</li><li>[stop-watching-all-url]</li></ul>")
  );

  $form['notifications']['templates']['watcher_notifications_templates_footer_confirm'] = array(
      '#type' => 'textarea',
      '#title' => t('Message Footer in Confirmation Messages'),
      '#default_value' => variable_get('watcher_notifications_templates_footer_confirm', $templates['footer_confirm']),
      '#description' => t("<p>The text above will be appended to confirmation messages sent to anonymous users when they start watching a node. Links for stopping watching this or every node the user watches and user's IP address are automatically prepended to this text.</p><ul><li>[user-ip]</li><li>[stop-watching-url]</li><li>[stop-watching-all-url]</li></ul>")
  );

  $form['notifications']['templates']['tokens_footer'] = array(
      '#type' => 'fieldset',
      '#title' => t('Additional Tokens for Footers'),
      '#collapsible' => true,
      '#collapsed' => true,
  );

  $form['notifications']['templates']['tokens_footer'][] = array(
        '#value' => theme('token_help', 'global'),
  );

  // Template for Messages about Node Updates

  $form['notifications']['templates']['watcher_notifications_templates_body_node'] = array(
      '#type' => 'textarea',
      '#title' => t('Message Body for Node Updates'),
      '#default_value' => variable_get('watcher_notifications_templates_body_node', $templates['body_node_update']),
      '#description' => t('<p>The following tokens are available:</p><ul><li>[node-url]</li></ul>')
  );

  $form['notifications']['templates']['tokens_nodes'] = array(
      '#type' => 'fieldset',
      '#title' => t('Additional Tokens for Nodes'),
      '#collapsible' => true,
      '#collapsed' => true,
  );

  $form['notifications']['templates']['tokens_nodes'][] = array(
     '#value' => theme('token_help', 'node'),
  );

  // Template for Messages about New Comments

  $form['notifications']['templates']['watcher_notifications_templates_body_cmt'] = array(
      '#type' => 'textarea',
      '#title' => t('Message Body for New Comments'),
      '#default_value' => variable_get('watcher_notifications_templates_body_cmt', $templates['body_comment_insert']),
      '#description' => t('<p>The following tokens are available:</p><ul><li>[comment-excerpt]</li><li>[node-url]</li><li>[comment-url]</li><li>[comment-reply-url]</li></ul>')
  );

  $form['notifications']['templates']['tokens_comments'] = array(
      '#type' => 'fieldset',
      '#title' => t('Additional Tokens for Comments'),
      '#collapsible' => true,
      '#collapsed' => true,
  );

  $form['notifications']['templates']['tokens_comments'][] = array(
      '#value' => theme('token_help', 'comment') . theme('token_help', 'node'),
  );

  // Template for Confirmation Messages to anonymous users

  $form['notifications']['templates']['watcher_notifications_templates_body_confirm'] = array(
      '#type' => 'textarea',
      '#title' => t('Message Body for Confirmation Messages'),
      '#default_value' => variable_get('watcher_notifications_templates_body_confirm', $templates['body_confirm']),
      '#description' => t('<p>The text above will be included in confirmation messages sent to anonymous users when they start watching a node.:</p><p>The following tokens are available:</p><ul><li>[node-url]</li></ul>')
  );
}

/**
 * Settings concerning default user settings
 * @param $form
 */
function _watcher_admin_settings_default_user_settings(&$form) {
  // Get the number of users that have not customized their settings
  $stats = _watcher_db_stats_user_settings();

  $form['default'] = array(
    '#type' => 'fieldset',
    '#title' => t('Default User Settings'),
    '#description' => t('These will set the defaults in every user\'s personal settings. Settings you make here will affect the settings of every user that hasn\'t customized his or her settings. This setting will currently affect <strong>!numusers</strong> users who have not customized their settings.', array('!numusers' => $stats['num_users_no_settings'])),
    '#collapsible' => true,
    '#collapsed' => true,
  );

  $form['default']['watcher_default_settings'] = array(
    '#type' => 'checkboxes',
    '#default_value' => variable_get('watcher_default_settings', array()),
    '#options' => array(
      'watcher_automatic_enable_notifications' => t('Automatically enable email notifications for posts that I start watching'),
      'watcher_notifications_updates' => t('Send email notification when a post is updated or edited'),
      'watcher_notifications_new_comments' => t('Send email notification when a post receives a new comment'),
      'watcher_autowatch_commented_on' => t('Automatically watch posts that I comment on'),
      'watcher_autowatch_posted' => t('Automatically watch posts that I make'),
      'watcher_share_binder' => t('Share my list of watched posts'),
    ),
  );
}

/**
 * Settings concerning the help page
 * @param $form
 */
function _watcher_admin_settings_help(&$form) {
  global $user;

  // Load notification message templates
  $templates = _watcher_admin_settings_field_value_templates();

  $form['help'] = array(
    '#type' => 'fieldset',
    '#title' => t('Help Page'),
    '#description' => t('This help text is displayed if the user clicks the Help link in the list of watched posts. You can customize it below. To apply your own CSS rules to this page you can use the <code>#watcher_help_page</code> selector.'),
    '#collapsible' => true,
    '#collapsed' => true,
  );

  $form['help']['watcher_help_text'] = array(
    '#type' => 'textarea',
    '#title' => t('Text on Help Page'),
    '#default_value' => variable_get('watcher_help_text', $templates['watcher_help_text']),
    '#rows' => 20,
  );

  $form['help'][] = array(
    '#value' => l(t('Preview help page'), "user/$user->uid/watcher/help"),
  );
}

/**
 * Returns statistics for the module
 */
function _watcher_admin_stats() {
  $form = array();

  //////////////// Notification Queue Table Statistics ////////////////

  $stats = _watcher_db_stats_notification_queue();

  $form['notification_queue_table'] = array(
    '#type' => 'fieldset',
    '#title' => t('Notification Queue Table'),
  );

  if ($stats['num_rows'] > 0) {
    $table_body = array(
    array(t('Number of notifications in queue'), $stats['num_rows']),
    array(t('Notification counter (some kind of indication of how many notifications that have been sent)'), $stats['num_sent']),
    );

    $form['notification_queue_table'][] = array(
        '#value' => theme('table', array(), $table_body),
    );


    $form['notification_queue_table']['content'] = array(
        '#type' => 'fieldset',
        '#title' => t('Table Content'),
        '#description' => t('This is the content of the notification queue table.'),
        '#collapsed' => true,
        '#collapsible' => true,
    );
    $form['notification_queue_table']['content'][] = array(
        '#value' => theme('table', $stats['data']['header'], $stats['data']['content']),
    );
  }
  else {
    $form['notification_queue_table'][] = array(
      '#value' => t('There are no notifications in the queue.'),
    );
  }
  //////////////// Watched Nodes Table Statistics ////////////////

  $stats = _watcher_db_stats_watched_nodes_users();

  $form['watched_nodes_users_table'] = array(
    '#type' => 'fieldset',
    '#title' => t('Watched Nodes Table'),
  );

  $table_body = array(
  array(t('Number of registered users that watch nodes'), $stats['num_users_reg']),
  array(t('Number of anonymous users that watch nodes'), $stats['num_users_anon']),
  );

  $form['watched_nodes_users_table'][] = array(
        '#value' => theme('table', array(), $table_body),
  );


  //////////////// User Settings Table Statistics ////////////////

  $stats = _watcher_db_stats_user_settings();

  $form['user_settings_table'] = array(
    '#type' => 'fieldset',
    '#title' => t('User Settings Table'),
  );

  $table_body = array(
  array(t('Number of users that have changed their user settings'), $stats['num_users_with_settings']),
  array(t('Number of users that use default user settings'), $stats['num_users_no_settings']),
  );

  $form['user_settings_table'][] = array(
        '#value' => theme('table', array(), $table_body),
  );


  return $form;

}

/**
 * Functions for testing the module's functionality
 */
function _watcher_admin_test() {

  global $user;

  $form = array();

  if (!variable_get('watcher_email_notifications_enabled', 1)) {
    $form[] = array(
      '#value' => '<p><strong>'. t('Email notifications are NOT enabled. You can use the diagnostics tools anyway but regular notifications will not be sent.') .'</strong></p>',
    );
  }

  $form[] = array(
    '#value' => t("If you're experiencing problems with the module such as messages not being delivered et c, you can use the diagnostic tools on this page to send test notifications. The <a href=\"!statpage\">statistics page</a> also contains valuable information for troubleshooting.", array('!statpage' => url('admin/settings/watcher/stats')))
  );

  $form['create_test_notification'] = array(
    '#type' => 'fieldset',
    '#title' => t('Create a Test Notification'),
    '#description' => t('Click the buttons below to add a notification to the queue. A notification for a new comment will use content from the newest comment on the site. A notification for a node update will contain a link to the newest node on the site. Notifications will be sent to your email account (!usermail).', array('!usermail' => $user->mail))
  );

  $form['create_test_notification']['create_notification_node_update'] = array(
        '#type' => 'submit',
        '#value' => t('Create test notification for node update'),
        '#submit' => array('_watcher_admin_test_create_node_update_notification'),
  );

  $form['create_test_notification']['create_notification_new_comment'] = array(
        '#type' => 'submit',
        '#value' => t('Create test notification for new comment'),
        '#submit' => array('_watcher_admin_test_create_new_comment_notification'),
  );

  $form['send_notifications_in_queue'] = array(
    '#type' => 'fieldset',
    '#title' => t('Send Notifications in Queue'),
    '#description' => t('Notifications are stored in a queue. Depending on what method you have chosen in the <a href="!settings">settings</a>, notifications are either delivered instantaneously (when a comment is posted or a node updated) or when cron is run.', array('!settings' => url('admin/settings/watcher/settings')))
  );

  $method = variable_get('watcher_email_notifications_method', 'cron');

  $form['send_notifications_in_queue'][] = array(
      '#value' => '<p>'. t('Your currently selected method for sending notifications in queue is <strong>!method</strong>.', array('!method' => $method)) .'</p>',
  );

  $stats = _watcher_db_stats_notification_queue();

  $form['send_notifications_in_queue']['stats'] = array(
      '#value' => '<p>'. t('There are currently <strong>!num</strong> !notification_plural in the queue.', array('!num' => $stats['num_rows'], '!notification_plural' => format_plural($stats['num_rows'], 'notification', 'notifications')))  .'</p>',
  );

  if ($stats['num_rows']) {
    if ($method == 'cron' ) {
      $form['send_notifications_in_queue']['cron'] = array(
          '#value' => '<p><strong>'. l(t('Click here to run cron manually and SEND messages in queue'), 'admin/reports/status/run-cron', array('query' => 'destination=admin/settings/watcher/test')) .'</strong></p>',
      );
    }
    else {
      $form['send_notifications_in_queue']['instant'] = array(
          '#type' => 'submit',
          '#value' => t('Click here to SEND messages in queue'),
          '#submit' => array('_watcher_admin_test_send_notifications_in_queue_instant'),
      );
    }

    $form['send_notifications_in_queue'][] = array(
      '#value' => '<p>- or -</p>',
    );

    $form['send_notifications_in_queue']['empty_queue'] = array(
      '#type' => 'submit',
      '#value' => t('Click here to EMPTY queue'),
      '#submit' => array('_watcher_admin_test_empty_queue')
    );
  }

  return $form;
}

/**
 * Submit handler for test form: Create node update notification
 */
function _watcher_admin_test_create_node_update_notification($form, &$form_state) {
  global $user;

  // Prepare an array of recipients
  $recipients = array($user->uid => $user);

  if ($newest_nid = _watcher_db_get_newest_node()) {
    $node = node_load($newest_nid);
    $subject = t('TEST: Post has been updated');
    if (_watcher_email_notify_node_update_add_to_queue($recipients, $subject, $node)) {
      drupal_set_message(t('A test notification (node update) has been created.'));
    }
  }
  else {
    drupal_set_message(t('No nodes exist. You need to create at least one.'), 'error');
  }
}

/**
 * Submit handler for test form: Create new comment notification
 */
function _watcher_admin_test_create_new_comment_notification($form, &$form_state) {

  global $user;

  // Prepare an array of recipients
  $recipients = array($user->uid => $user);

  if ($newest_cid = _watcher_db_get_newest_comment()) {
    $comment = (array) _comment_load($newest_cid);
    $subject = t('TEST: New comment posted');
    if (_watcher_email_notify_comment_insert_add_to_queue($recipients, $subject, $comment)) {
      drupal_set_message(t('A test notification (new comment) has been created.'));
    }
  }
  else {
    drupal_set_message(t('No comments exist. You need to make at least one.'), 'error');
  }
}

/**
 * Submit handler for test form: Send notifications in queue instantly
 */
function _watcher_admin_test_send_notifications_in_queue_instant($form, &$form_state) {
  _watcher_static('messages to send', true);
  drupal_set_message(t('Notifications have been sent using instant method.'));
}

/**
 * Submit handler for test form: Empty notification queue
 */
function _watcher_admin_test_empty_queue($form, &$form_state) {
  if (_watcher_db_delete_users_notification_queue_all()) {
    drupal_set_message(t('Watcher Notification Queue has been emptied.'));
  }
}


/***************************************************************************************
 * PAGES
 ***************************************************************************************/

/**
 * Displays the user's binder
 *
 * @param $account
 *     User object for the user whose list we want to display
 */
function _watcher_binder($account) {

  global $user;

  // Is the current user the owner of this binder?
  $user_is_owner = ($user->uid == $account->uid);

  // Are email notifications enabled?
  $emails_enabled = variable_get('watcher_email_notifications_enabled', 1);

  // Load account of binder owner
  $owner = $account;
  $uid = $owner->uid;

  // Set title
  drupal_set_title(t('!username\'s Watched Posts', array('!username' => $owner->name)));


  // Make localized strings for JavaScript
  $tstrings = array();
  $tstrings['binder_notif_text_enabled'] = t('Click to Disable');
  $tstrings['binder_notif_text_disabled'] = t('Click to Enable');
  $tstrings['binder_notif_title_enabled'] = t('Email notifications for this post are ENABLED, click to disable');
  $tstrings['binder_notif_title_disabled'] = t('Email notifications for this post are DISABLED, click to enable');
  $tstrings['module_path'] = base_path() . drupal_get_path('module', 'watcher');

  // Add CSS
  drupal_add_css(drupal_get_path('module', 'watcher') .'/css/watcher.css');

  // Add JS for AJAX
  drupal_add_js(drupal_get_path('module', 'watcher') .'/js/watcher.js');
  drupal_add_js(array('watcher' => $tstrings), 'setting');

  //////////// Set up table header ////////////////
  $header = array();

  // type
  if (variable_get('watcher_display_node_type', 0)) {
    $header[] = t('Type');
  }

  // post title
  $header[] = array(
    'data' => t('Post'),
    'field' => 'n.title',
  );

  // post author
  if (variable_get('watcher_display_post_author', true)) {
    $header[] = array(
      'data' => t('Author'),
      'field' => 'u.name',
    );
  }

  // time post was added to watched list
  $header[] = array(
    'data' => t('Post Added'),
    'field' => 'wn.added',
    'sort' => 'desc',
  );

  // last post updated time
  if (variable_get('watcher_display_last_updated', 0)) {
    $header[] = array(
      'data' => t('Last updated'),
      'field' => 'last_updated',
    );
  }

  // Comment and new comments count
  $header[] = t('Comments');

  // Email notification toggle icon
  if ($user_is_owner && $emails_enabled) {
    $header[] = t('Notifications');
  }

  // Watch post toggle icon
  if ($user_is_owner) {
    $header[] = t('Remove');
  }


  ////////////  Obtain nodes the user is watching //////////
  $result = _watcher_db_get_watched_nodes_full($owner, $header);


  ///////////// Process the result set //////////////
  $rows = array();
  while ($node = db_fetch_object($result)) {

    // Determine the number of comments:
    $comments = _watcher_binder_comment_count($node);

    // Generate destination and token query
    $link_query = drupal_get_destination() ."&token=". _watcher_get_token($node->nid);

    // Populate table
    $row = array();

    // Get name of node type
    if (variable_get('watcher_display_node_type', 0)) {
      $row[] = check_plain(node_get_types('name', $node->type));
    }

    // Node title
    $row[] = l($node->title, "node/$node->nid") .' '. theme('mark', node_mark($node->nid, $node->changed));

    // Author user name
    if (variable_get('watcher_display_post_author', 0)) {
      $row[] = theme('username', $node);
    }

    // Post added
    $row[] = ($node->added ? t('!time ago', array('!time' => format_interval(time() - $node->added))) : null );

    // Post last updated
    if (variable_get('watcher_display_last_updated', 0)) {
      $row[] = t('!time ago', array('!time' => format_interval(time() - $node->last_updated)));
    }

    // Number of comments
    $row[] = array(
      'class' => 'replies',
      'data' => $comments
    );

    // Email notification status icon
    if ($user_is_owner && $emails_enabled) {
      $row[] = theme('watcher_binder_email_icon', $tstrings, ($node->send_email ? 'enabled' : 'disabled'), "user/$uid/watcher/email_notifications_toggle/$node->nid", $link_query);
    }

    // Stop watching icon
    if ($user_is_owner) {
      $row[] = theme('watcher_binder_stop_watching_icon', "user/$uid/watcher/toggle/$node->nid", $link_query);
    }

    $rows[] = $row;
  }

  // If there are no posts
  if (!$rows) {
    $rows[] = array(
    array(
        'data' => ( $user_is_owner ?  t('No posts in the list - you should add some!') : t('!username\'s watched posts list is empty', array('!username' => $owner->name))),
        'colspan' => count($header),
    )
    );
  }

  // Load notification message templates
  $templates = _watcher_admin_settings_field_value_templates();

  //////////////// Generate page content /////////////
  $intro_text = filter_xss_admin(t(variable_get('watcher_display_intro_text', $templates['watcher_display_intro_text'])));
  $output = theme('watcher_binder', $intro_text, $header, $rows);

  return $output;
}

/**
 * Redirection to user/nid/watcher
 */
function _watcher_binder_shortcut() {
  $uid = $GLOBALS['user']->uid;
  drupal_goto("user/$uid/watcher/binder");
}

/**
 * Provide a user settings page for the module
 */
function _watcher_user_settings(&$form_state, $account) {

  global $user;

  // Add CSS
  drupal_add_css(drupal_get_path('module', 'watcher') .'/css/watcher.css');

  // Set page title
  drupal_set_title(t('Settings for Watching Posts'));

  // Load account of binder owner
  $owner = $account;
  $uid = $owner->uid;

  $form = array();

  $defaults = !((bool) _watcher_user_settings_load('watcher_custom', $uid));
  $defaults_text = t('Your settings are currently defaults, as set by the site owner. Save these settings to customize them.');

  if ($defaults) {
    $form[] = array(
      '#value' => theme('watcher_settings_defaults_notice', $defaults_text),
    );
  }

  if (variable_get('watcher_email_notifications_enabled', 1)) {

    $form['email'] = array(
      '#type' => 'fieldset',
      '#title' => t('Email Notification Settings'),
      '#collapsible' => false,
      '#collapsed' => false,
    );

    $form['email']['watcher_automatic_enable_notifications'] = array(
        '#type' => 'checkbox',
        '#title' => t('Automatically enable email notifications for posts that I start watching'),
        '#default_value' => _watcher_user_settings_load('watcher_automatic_enable_notifications', $uid),
    );

    $form['email'][] = array(
        '#value' => '<div style="height: .5em"></div>'
        );

        $form['email']['watcher_notifications_updates'] = array(
        '#type' => 'checkbox',
        '#title' => t('Send email notification when a post is updated or edited'),
        '#default_value' => _watcher_user_settings_load('watcher_notifications_updates', $uid),
        );

        $form['email']['watcher_notifications_new_comments'] = array(
        '#type' => 'checkbox',
        '#title' => t('Send email notification when a post receives a new comment'),
        '#default_value' => _watcher_user_settings_load('watcher_notifications_new_comments', $uid),
        );

  }

  $form['autowatch'] = array(
    '#type' => 'fieldset',
    '#title' => t('Watch Automatically'),
    '#collapsible' => false,
    '#collapsed' => false,
  );

  $form['autowatch']['watcher_autowatch_commented_on'] = array(
      '#type' => 'checkbox',
      '#title' => t('Automatically watch posts that I comment on'),
      '#default_value' => _watcher_user_settings_load('watcher_autowatch_commented_on', $uid),
  );

  $form['autowatch']['watcher_autowatch_posted'] = array(
      '#type' => 'checkbox',
      '#title' => t('Automatically watch posts that I make'),
      '#default_value' => _watcher_user_settings_load('watcher_autowatch_posted', $uid),
  );

  $form['sharing'] = array(
    '#type' => 'fieldset',
    '#title' => t('Sharing'),
    '#description' => t('By sharing your list of watched posts, other users can see it but they cannot remove or add posts or change it in any way.'),
    '#collapsible' => false,
    '#collapsed' => false,
  );

  $form['sharing']['watcher_share_binder'] = array(
      '#type' => 'checkbox',
      '#title' => t('Share my list of watched posts'),
      '#default_value' => _watcher_user_settings_load('watcher_share_binder', $uid),
  );

  $form['uid'] = array(
    '#type' => 'hidden',
    '#value' => $uid,
  );

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save my settings')
  );

  return $form;

}

/**
 * Display the help text of the module
 */
function _watcher_help_page() {
  // Add CSS
  drupal_add_css(drupal_get_path('module', 'watcher') .'/css/watcher.css');

  // Set page title
  drupal_set_title(t('Help on Watching Posts'));

  // Load notification message templates
  $templates = _watcher_admin_settings_field_value_templates();

  // Check text for XSS
  $text = filter_xss_admin(variable_get('watcher_help_text', $templates['watcher_help_text']));

  // Return a themed help page
  return theme('watcher_help_page', $text);
}


/***************************************************************************************
 * MENU CALLBACKS
 ***************************************************************************************/

/**
 * Toggle watching/unwatching a post
 *
 * @param $node
 *     Node object of the node we want to toggle watching for
 * @param $mail
 *     String passed for anonymous users unwatching, the email address of the users
 * @param $unwatch
 *     String passed for anonymous users unwatching, 'node' to unwatch given node,
 *     'all' to unwatch all nodes associated with given $mail
 * @param $confirm
 *     String, passed if the user is confirming an action such as unwatching all posts on the site
 *
 * TODO: Refactor into several functions
 */
function _watcher_watch_toggle($node, $mail = null, $unwatch = null, $confirm = null) {
  $nid = $node->nid;
  $token = $_REQUEST['token'];

  // Validate token
  if (_watcher_get_token($node->nid) != $token) {
    drupal_set_message(t("You've followed an invalid link."));
    drupal_access_denied();
    return;
  }

  global $user;

  // Are email notifications enabled?
  $email_notifications = variable_get('watcher_email_notifications_enabled', true);

  // Is this an anonymous user?
  $anonymous = !((bool) $user->uid);

  // If this is an anonymous user, we check to see if the user is already watching this node in current session
  if ($anonymous) {
    if (is_array($watches = _watcher_session('watching'))) {
      $watching = in_array($nid, $watches);
    }
    // Load user's email address from session
    $user->mail = _watcher_session('mail');
  }

  // Is this an asynchronous request?
  $async = $_GET['async'];

  // Get destination for redirection later on
  $dest = drupal_get_destination();

  // Handle an unwatch request for an anonymous user
  // The user doesn't have to be the current user, it can be someone else so we load the default
  // anonymous user object and add a mail property

  if ($mail && $unwatch) {
    $recipient_user = user_load(0);
    $recipient_user->mail = $mail;

    // Unwatching a single node
    if ($unwatch == 'node') {
      $nids = array($nid);
      if (_watcher_user_unset_watching_nodes(array($nid), $recipient_user)) {
        $message = t('%ntitle previously watched by %email is no longer being watched.', array('%ntitle' => check_plain($node->title), '%email' => check_plain($recipient_user->mail)));

        // Remove NID from session to mark it as no longer being watched
        $nids = _watcher_session('watching');
        if (is_array($nids)) {
          unset($nids[$nid]);
          _watcher_session('watching', $nids);
        }
      }
    }

    // Unwatching all nodes being watched

    if ($unwatch == 'all') {
      if ($confirm != 'confirm') {

        $link_query = "token=$token";

        // Confirm the action
        $out = array();
        $out[] = array(
          '#value' => '<h3>' . t('Are you sure you want to STOP watching all posts %email currently watches on this site? This action cannot be undone.', array('%email' =>  check_plain($recipient_user->mail))) . '</h3>' . '<p style="font-weight: bold;">'. l('Yes, I am sure.', 'user/'. $user->uid .'/watcher/toggle/'. $nid .'/unwatch-all/'. $mail .'/confirm', array('query' => $link_query)) .'</p>',
        );
        return drupal_render($out);
      }
      else {
        if ($db_result = _watcher_db_get_watched_nodes_mini($recipient_user)) {

          // Remove NID from session to mark it as no longer being watched
          $nids = _watcher_session('watching');
          while ($row = db_fetch_object($db_result)) {
            $watched_nids[] = $row->nid;
            if (is_array($nids)) {
              unset($nids[$row->nid]);
            }
          }
          _watcher_session('watching', $nids);

          // Unwatch nodes
          _watcher_user_unset_watching_nodes($watched_nids, $recipient_user);
          $message = t('All posts on this site previously watched by %email are no longer being watched.', array('%email' => check_plain($recipient_user->mail)));
        }
      }
    }
  }


  // Handle watching for anonymous users

  elseif ($anonymous) {
    // Check if email notifications are enabled before displaying email address form
    if ($email_notifications) {
      // If this is the result of a form submission or regular request, render form in HTML
      // otherwise return JSON
      $watch_action = ($watching ? 'unwatch' : 'watch');
      if ($_POST || !$async) {
        return drupal_get_form('_watcher_watch_toggle_anonymous_form', $nid, $watch_action);
      }
      $nstatus['status'] = 'form';
      $nstatus['data'] = drupal_get_form('_watcher_watch_toggle_anonymous_form', $nid, $watch_action);
    }
  }


  // Handle watching and unwatching for logged-in users
  else {

    $link_query = $dest ."&token=$token";

    if (_watcher_user_is_watching_node($user, $nid)) {
      if (_watcher_user_unset_watching_nodes(array($nid), $user)) {
        $message = t('You are no longer watching <em>!ntitle</em>. <strong><a href="!undo">Undo</a></strong>', array('!ntitle' => check_plain($node->title), '!undo' => url("user/$user->uid/watcher/toggle/$node->nid", array('query' => $link_query))));
        $nstatus['status'] = 'disabled';
      }
      else {
        $message = t('We\'re sorry but you may not watch this type of post.');
      }
    }
    else {
      if (_watcher_user_set_watching_node($nid, $user)) {
        $message = t('You are now watching <em>!ntitle</em>. <strong><a href="!undo">Undo</a></strong>', array('!ntitle' => check_plain($node->title), '!undo' => url("user/$user->uid/watcher/toggle/$node->nid", array('query' => $link_query))));
        $nstatus['status'] = 'enabled';
      }
      else {
        $message = t('We\'re sorry but you may not watch this type of post.');
      }
    }
  }

  // If this was an asynchronous request, see if a form was generated then output it
  // otherwise output of status of request
  if ($async) {
    print drupal_to_js($nstatus);
  }
  else {
    drupal_set_message($message);
    drupal_goto();
  }
}

/**
 * Toggle email notifications for a post
 *
 * @param $nid Node ID of the node we want to toggle notifications forr
 */
function _watcher_email_notifications_toggle($nid) {
  global $user;
  $uid = $user->uid;

  $token = $_REQUEST['token'];

  // Validate token
  if (_watcher_get_token($nid) != $token) {
    drupal_set_message(t("You've followed an invalid link."));
    drupal_access_denied();
    return;
  }

  // Toggle
  if (_watcher_user_is_watching_node($user, $nid)) {
    if (_watcher_email_notifications_status_for_user_node($nid, $uid)) {
      _watcher_email_notifications_disable($nid, $uid);
      $nstatus['status'] = 'disabled';
    }
    else {
      _watcher_email_notifications_enable($nid, $user);
      $nstatus['status'] = 'enabled';
    }
  }

  // Is this an asynchronous request?
  $async = $_GET['async'];

  // If this was an asynchronous request - output JSON, otherwise redirect
  if ($async) {
    print drupal_to_js($nstatus);
  }
  else {
    drupal_goto();
  }

}


/***************************************************************************************
 * SUPPORT FUNCTIONS
 ***************************************************************************************/

/**
 * Enable Email Notifications for a User for a Node
 *
 * @param $nid
 *    Node NID
 *
 * @param $user
 *    A user object
 */
function _watcher_email_notifications_enable($nid, $user) {
  return _watcher_db_set_send_email_status($nid, $user, 1);
}

/**
 * Disable Email Notifications for a Node
 *
 * @param $nid
 *    Node NID
 *
 * @param $uid
 *    User UID
 */
function _watcher_email_notifications_disable($nid, $uid) {
  return _watcher_db_set_send_email_status($nid, $uid, 0);
}

/**
 * Does the user receive email notifications for this node?
 *
 * @param $nid
 *    Node NID
 *
 * @param $uid
 *    User UID
 */
function _watcher_email_notifications_status_for_user_node($nid, $uid) {
  return _watcher_db_get_send_email_status($nid, $uid);
}

/**
 * Send notification when a node has been updated
 *
 * @param $node
 *    A node object.
 */
function _watcher_email_notify_node_update(&$node) {
  global $user;

  // Are email notifications enabled?
  $email_notifications = variable_get('watcher_email_notifications_enabled', true);
  if (!$email_notifications) {
    return false;
  }

  // Since a node may be edited by many, we want to exclude the user who just
  // edited it (who may not necessarily be its author), so we exclude current
  // user's UID.

  // TODO: Exclusion by email address, allows exclusion of anonymous users who
  //       still have their email address in session and who we therefore can
  //       identify.

  // Who are the users that want to be notified of this event?
  $recipients = _watcher_db_get_users_notify_node_update($node->nid, $user->uid);

  // Set up subject
  $node_title = check_plain($node->title ? ' "'. substr($node->title, 0, 128) .'" ' : false);
  $site_name = variable_get('site_name', false ? ' ('. check_plain(variable_get('site_name', '')) .')' : '');
  $subject = t('Post!nodetitlehas been updated!sitename', array('!nodetitle' => $node_title, '!sitename' => $site_name));

  // Add to queue
  _watcher_email_notify_node_update_add_to_queue($recipients, $subject, $node);

  // Flag that there are messages in queue
  _watcher_static('messages to send', true);
}

/**
 * Add node update notification to the notification queue
 *
 * @param $recipients
 *    An array with UID as key of recipient user objects with attributes name, mail and uid
 * @param $subject
 *    The subject of the notification
 * @param $node
 *    A node object of the node that has been updated
 */
function _watcher_email_notify_node_update_add_to_queue($recipients, $subject, $node) {
  foreach ($recipients as $recipient) {

    $message = '';

    // Get message header with tokens replaced
    // Do not include headers for anonymous recipients (they have no user name)
    if ($recipient->uid) {
      $message .= _watcher_email_notifications_replace_tokens_header_and_footer($recipient, $node->nid, 'header');
    }

    // Get message body with tokens replaced
    $message .= _watcher_email_notifications_replace_tokens_node_update($node, $recipient);

    // Get message footer with tokens replaced
    // Use different templates depending on whether this is a regular or anonymous recipient
    if ($recipient->uid) {
      $message .= _watcher_email_notifications_replace_tokens_header_and_footer($recipient, $node->nid, 'footer');
    }
    else {
      $message .= _watcher_email_notifications_replace_tokens_header_and_footer($recipient, $node->nid, 'footer_anonymous_notify');
    }

    // Add to message queue
    $res = _watcher_email_notifications_add_to_queue($recipient, $subject, $message) && ( isset($res) ? $res : true );
  }
  return $res;
}

/**
 * Send notification when a comment has been posted
 *
 * @param $comment
 *    A comment object as provided by hook_comment
 */
function _watcher_email_notify_comment_insert(&$comment) {

  // Are email notifications enabled?
  $email_notifications = variable_get('watcher_email_notifications_enabled', true);
  if (!$email_notifications) {
    return false;
  }

  // Since a comment is by necessity authored by the current user, we can safely assume
  // that the current user authored it and use the comment's UID for exclusion.

  // Who are the users that want to be notified of this event?
  $recipients = _watcher_db_get_users_notify_comment_insert($comment['nid'], $comment['uid']);
  // No need to go any further unless we have to
  if (!count($recipients)) {
    return;
  }

  // Load node to get its title
  $node = node_load($comment['nid']);

  // Set up subject
  $node_title = ( $node->title ? t(' about "!nodetitle"', array('!nodetitle' => substr($node->title, 0, 128))) : false );
  $site_name = ( variable_get('site_name', false ) ? ' ('. variable_get('site_name', '') .')' : '' );
  $subject = t('New comment posted!aboutnodetitle!sitename', array('!aboutnodetitle' => $node_title, '!sitename' => $site_name));

  // Add to queue
  _watcher_email_notify_comment_insert_add_to_queue($recipients, $subject, $comment);

  // Flag that there are messages in queue
  _watcher_static('messages to send', true);
}

/**
 * Add comment insert notification to the notification queue
 *
 * @param $recipients
 *    An array with UID as key of recipient user objects with attributes name, mail and uid
 * @param $subject
 *    The subject of the notification
 * @param $node
 *    The object of the node the anonymous user is now watching
 */
function _watcher_email_notify_comment_insert_add_to_queue($recipients, $subject, $comment) {
  foreach ($recipients as $recipient) {

    $message = '';

    // Get message header with tokens replaced
    // Do not include headers for anonymous recipients (they have no user name)
    if ($recipient->uid) {
      $message .= _watcher_email_notifications_replace_tokens_header_and_footer($recipient, $comment['nid'], 'header');
    }

    // Get message body with tokens replaced
    $message .= _watcher_email_notifications_replace_tokens_comment_insert($comment, $recipient);

    // Get message footer with tokens replaced
    // Use different templates depending on whether this is a regular or anonymous recipient
    if ($recipient->uid) {
      $message .= _watcher_email_notifications_replace_tokens_header_and_footer($recipient, $comment['nid'], 'footer');
    }
    else {
      $message .= _watcher_email_notifications_replace_tokens_header_and_footer($recipient, $comment['nid'], 'footer_anonymous_notify');
    }

    // Add to message queue
    $res = _watcher_email_notifications_add_to_queue($recipient, $subject, $message) && ( isset($res) ? $res : true );
  }
  return $res;
}

/**
 * Send a confirmation message upon anonymous user starting to watch a node
 *
 * @param $user
 *     A user object
 * @param $nid
 *     NID of node being watched
 */
function _watcher_email_notify_anonymous_watch_confirm($user, $nid, $what = null) {

  // Are email notifications enabled?
  $email_notifications = variable_get('watcher_email_notifications_enabled', true);
  if (!$email_notifications) {
    return false;
  }

  // Add user to recipients
  $recipients[] = $user;

  // Load node
  $node = node_load($nid);

  // Adapt subject depending on what the user is watching for
  $subj_for_what['all'] = ' '. t('for updates and new comments');
  $subj_for_what['comments'] = ' '. t('for new comments');
  $subj_for_what['updates'] = ' '. t('for updates');

  // Set up subject
  $node_title = check_plain($node->title ? t(' "!nodetitle"', array('!nodetitle' => substr($node->title, 0, 128))) : false);
  $site_name = ( variable_get('site_name', false ) ? ' ('. variable_get('site_name', '') .')' : '' );
  $subject = t('You are now watching!aboutnodetitle!forwhat!sitename', array('!aboutnodetitle' => $node_title, '!forwhat' => $subj_for_what[$what], '!sitename' => $site_name));

  // Add to queue
  _watcher_email_notify_anonymous_watch_confirm_add_to_queue($recipients, $subject, $node);

  // Flag that there are messages in queue
  _watcher_static('messages to send', true);

}

/**
 * Add confirm anonymous watching notification to the notification queue
 *
 * @param $recipients
 *    An array with UID as key of recipient user objects with attributes name, mail and uid
 * @param $subject
 *    The subject of the notification
 * @param $node
 *    Node object of the node that is now being watched
 */
function _watcher_email_notify_anonymous_watch_confirm_add_to_queue($recipients, $subject, $node) {
  foreach ($recipients as $recipient) {
    // Get message body with tokens replaced
    $message .= _watcher_email_notifications_replace_tokens_anonymous_watch_confirm($node, $recipient);

    // Get message footer with tokens replaced
    $message .= _watcher_email_notifications_replace_tokens_header_and_footer($recipient, $node->nid, 'footer_confirm');

    // Add to message queue
    $res = _watcher_email_notifications_add_to_queue($recipient, $subject, $message) && ( isset($res) ? $res : true );
  }
  return $res;
}


/**
 * Is the user watching the node?
 *
 * @param $user
 *    A user object
 * @param $nid
 *    Node NID
 */
function _watcher_user_is_watching_node($user, $nid) {

  $uid = $user->uid;
  $mail = $user->mail;

  // Cache result for a given uid or email address,
  // this saves us a lot of unnecessary queries!
  static $result;

  // We have to look up anonymous users by email
  if (!$uid) {
    if (!isset($result[$mail])) {
      $result[$mail] = _watcher_db_get_nids_by_user($user);
    }
    return $result[$mail][$nid];
  }
  else {
    if (!isset($result[$uid])) {
      $result[$uid] = _watcher_db_get_nids_by_user($user);
    }
    return $result[$uid][$nid];
  }

}

/**
 * Set user watching post
 *
 * @param $nid
 *     Node NID
 * @param $user
 *     A user object
 * @param $what
 *     A string representing what the user wishes to watch for: all, comments, updates
 *
 * @return
 *     Boolean true on success, false on failure
 */
function _watcher_user_set_watching_node($nid, $user, $what = null) {

  // Check whether this is one of the node types for which Watcher is enabled
  if (!_watcher_node_type_enabled($nid)) {
    return false;
  }

  // For logged-in users, we check if the user is watching the node
  // For anonymous, we start watching regardless of whether the user was watching before or not
  if (($user->uid && !_watcher_user_is_watching_node($user, $nid)) || !$user->uid) {
    if (_watcher_db_set_nid_user($nid, $user, $what)) {
      // Automatically enable notifications for this node if the user has
      // set this option, it's in the defaults or the user is anonymous as
      // anonymous users must be notified as they do not have a binder
      if (_watcher_user_settings_load('watcher_automatic_enable_notifications') || !$user->uid) {
        _watcher_email_notifications_enable($nid, $user);
      }
      return true;
    }
  }
  return false;
}

/**
 * Unset user watching posts
 *
 * @param $nids
 *    Array of node NIDs
 * @param $user
 *    A user object
 *
 * @return
 *    A boolean indicating success
 */
function _watcher_user_unset_watching_nodes($nids, $user) {
  return (_watcher_db_delete_nids_user($nids, $user));
}

/**
 * Autowatch a node the user has posted
 *
 * @param $nid
 *    Node NID
 */
function _watcher_user_autowatch_node_insert($nid) {
  global $user;
  if (_watcher_user_settings_load('watcher_autowatch_posted')) {
    _watcher_user_set_watching_node($nid, $user);
  }
}

/**
 * Autowatch a node the user has commented on
 *
 * @param $comment
 *    A comment object as provided by hook_comment
 */
function _watcher_user_autowatch_comment_insert($comment) {
  global $user;

  if (_watcher_user_settings_load('watcher_autowatch_commented_on')) {
    _watcher_user_set_watching_node($comment['nid'], $user);
  }
}

/**
 * Return a link for watching/unwatching a post
 */
function _watcher_node_watch_link(&$node, $a3, $a4) {

  global $user;
  $anonymous = !((bool) $user->uid);

  // If email notifications are disabled, no link will be shown to anonymous users
  // as email notifications must be enabled for anonymous users to be notified
  // since they do not have their own binder
  $email_notifications = variable_get('watcher_email_notifications_enabled', true);
  if ($anonymous && !$email_notifications) {
    return;
  }

  // Do not display toggle watch link in teaser if admin has disabled link for teaser
  $link_in_teaser = variable_get('watcher_toggle_link_in_teaser', false);
  if ($a3 && !$link_in_teaser) {
    return false;
  }

  // Keep track of whether the module has been run multiple times
  static $called;

  // Is current user watching this node?
  if ($anonymous) {
    if (is_array($watches =  _watcher_session('watching'))) {
      $watching = in_array($node->nid, $watches);
    }
  }
  else {
    $watching = _watcher_user_is_watching_node($user, $node->nid);
  }

  $link_query = drupal_get_destination() ."&token=". _watcher_get_token($node->nid, $user->uid);

  // Make localized strings
  $tstrings = array();
  $tstrings['watch_toggle_enabled'] = t('You are watching this post, click to stop watching');
  $tstrings['watch_toggle_enabled_title'] = t('This post is being watched. You can track and change email notification setting for this post in your watched posts list (see options)');
  $tstrings['watch_toggle_disabled'] = t('You are not watching this post, click to start watching');
  $tstrings['watch_toggle_disabled_title'] = t('Watch posts to be notified when other users comment on them or the posts are changed');

  // Build link to the user's list of watched posts
  $tstrings['watch_watched_posts_link'] = l(t('Go to your list of watched posts'), 'user/'. $user->uid .'/watcher', array('attributes' => array('class' => 'watcher_node_help_link_to_binder')));

  // Add JS setting boolean variable indicating whether the current user is anonymous or not
  $settings['watcher'] = array('user_is_anonymous' => (bool) $user->uid);

  // Add JS for AJAX
  drupal_add_js(drupal_get_path('module', 'watcher') .'/js/watcher.js');

  // Load language strings into JS
  $settings['watcher'] += $tstrings;
  // We should only call drupal_add_js once or we'll end up with arrays instead of
  // strings in our JS settings
  if (!$called) {
    drupal_add_js($settings, 'setting');
    $called = true;
  }

  // Add CSS
  drupal_add_css(drupal_get_path('module', 'watcher') .'/css/watcher.css');

  //Add element to node
  $node->content['watcher'] = array(
    '#value' => theme('watcher_node_toggle_watching_link', $user->uid, $node->nid, $link_query, $watching, $tstrings),
    '#weight' => 30,
  );
}

/**
 * Save user settings for Watcher
 *
 * @param $settings
 *    An associative array of user settings, usually a subset of a form array
 * @param $uid
 *    UID of user who these settings apply to
 * @param $form
 *    A boolean denoting whether the function was called as a result from
 *    the user using the user settings form in their user profile
 */
function _watcher_user_settings_save($settings, $uid = null) {
  global $user;

  // Save to database
  $uid = ( $uid ? $uid : $user->uid );
  if (_watcher_db_set_user_settings($uid, $settings)) {
    drupal_set_message(t('Settings saved.'));
    return true;
  }

}

/**
 * Load user settings for Watcher
 *
 * @param $setting
 *     The setting to be loaded
 * @param $acct_uid
 *     The UID of the user account the setting applies to.
 *
 * @return
 *     The setting's value as a string or null.
 */
function _watcher_user_settings_load($setting, $acct_uid = null) {
  // Include DB dependent functions
  module_load_include('inc', 'watcher', 'watcher.db');

  global $user;
  $uid = ( is_numeric($acct_uid) ? $acct_uid : $user->uid );

  // Anonymous users cannot have settings
  if (!$uid) {
    return false;
  }

  // Strip watcher_
  if (preg_match('/^watcher_(.*)/', $setting, $matches)) {
    $setting = $matches[1];
  }

  // Cache settings
  static $settings;

  // Load from db if setting isn't cached
  if (!isset($settings[$setting])) {
    $settings = _watcher_db_get_user_settings($uid);
  }

  // Return the requested setting
  return $settings[$setting];
}

/**
 * Set passed default settings for user who have not customized their settings
 *
 * @param $default_settings
 *   An assoc array of user settings
 *
 */
function _watcher_user_settings_update_defaults($default_settings = null) {
  // Load defaults if not passed as argument
  if (!$default_settings) {
    $default_settings = _watcher_user_settings_load_defaults();
  }

  // DB query
  _watcher_db_user_settings_update_defaults($default_settings);
}

/**
 * Returns the default user settings either as passed as an argument or loaded from variables as an associative array
 *
 * @param $default_settings
 *    An associative array of default user settings, keys with watcher_ prefix
 *
 * @return
 *    An associate array of user settings
 */
function _watcher_user_settings_load_defaults($default_settings = null) {
  static $settings;

  if (!$settings) {
    // Load defaults
    $dsettings = ( $default_settings ? $default_settings : variable_get('watcher_default_settings', 0) );

    // If defaults have been set, generate an associative array
    if (is_array($dsettings)) {

      // Build query string elements
      foreach ($dsettings as $dsetting => $value) {

        // Strip watcher_
        preg_match('/^watcher_(.*)/', $dsetting, $matches);
        $setting = $matches[1];

        // Add new element
        $settings[$setting] = (int) (bool) $value;
      }
    }
  }

  // If all defaults are "off", the settings array contains an element with
  // an empty key which we need to delete
  unset($settings['']);

  // Return the finished array
  return $settings;
}

/**
 * Returns templates for some settings fields that are textareas
 */
function _watcher_admin_settings_field_value_templates() {

  // Heredoc syntax used for convenience.

  // Intro text on binder //////////////////////////////////////

  $templates['watcher_display_intro_text'] = <<<EOT

It's sometimes difficult to keep track of posts that are interesting or which you have been posting comments on. By <em>watching</em> posts that you want to keep an eye on you can see (and be notified) when they're updated or changed or when new comments are posted. This page lists all posts that you are currently watching. Click Help to learn more.

EOT;
  //'
  // Header //////////////////////////////////////

  $templates['header'] = <<<EOT

[recipient-username],

Your are receiving this email because you have chosen to receive email notifications from [site-name] ([site-url]). To change this, click the link below to go to your personal settings and disable email notifications:
[recipient-user-settings-url]

------------------------------------------

EOT;

  $templates['header'] .= "\r\n"; //add line breaks


  // Footer /////////////////////////////////////////////////////////////////////

  $templates['footer'] = "\r\n\r\n"; // add line breaks
  $templates['footer'] .= <<<EOT

------------------------------------------

To stop watching this post, click the link below:
[stop-watching-url]

If you believe you have received this message in error, please contact the site administrator at the following email address:
[site-mail]

Sincerely,

The staff at [site-name] - [site-slogan]

EOT;


  // Footer for confirmation messages to anonymous users ////////////////////////

  $templates['footer_confirm'] .= <<<EOT

-----------------------------------------

The request was made from IP address: [user-ip]

To stop watching THIS post, click here:
[stop-watching-url]

To stop watching ALL posts on this site, click here:
[stop-watching-all-url]

If you have any further questions, please contact the site administrator at the following email address:
[site-mail]

Sincerely,

The staff at [site-name] - [site-slogan]

EOT;


  // Footer for notification messages to anonymous users ////////////////////////

  $templates['footer_anonymous_notify'] .= <<<EOT

-----------------------------------------

To stop watching THIS post, click here:
[stop-watching-url]

To stop watching ALL posts on this site, click here:
[stop-watching-all-url]

If you have any further questions, please contact the site administrator at the following email address:
[site-mail]

Sincerely,

The staff at [site-name] - [site-slogan]

EOT;


  // Body for notifications on node updates /////////////////////////////////////

  $templates['body_node_update'] = <<<EOT

The following post has been updated:
[title]

Click the link below to view it:
[node-url]

EOT;


  // Body for notifications on new comments /////////////////////////////////////

  $templates['body_comment_insert'] = <<<EOT

[comment-author-name] wrote a new comment about '[title]':
[node-url]

Comment excerpt:
"[comment-excerpt]..."

To read the rest, view the entire comment by clicking the link below:
[comment-url]

Click the link below to reply to this comment:
[comment-reply-url]

EOT;

  // Body for confirmation messages to anonymous users //////////////////////////

  $templates['body_confirm'] = <<<EOT

A request to watch the following page has been made:
[node-url]

As long as the page is being watched you will receive email messages when
new comments or updates are made, depending on what you have chosen to
watch for.

If this request was made in error, please click either of the links below.
EOT;

  //'
  // Body for Watcher help text //////////////////////////////////////

  $templates['watcher_help_text'] = '<div id="watcher_help_page">';
  $templates['watcher_help_text'] .= <<<EOT

<p>It's sometimes difficult to keep track of posts that are interesting or which you have been posting comments on. By <em>watching</em> posts that you want to keep an eye on you can see (and be notified) when they're updated or changed or when new comments are posted.</p>

<h3>Basic use</h3>
<ul>
<li>To start watching a post, click the link you see by the post on the site. The post is now added to your list of watched posts. Click again to stop watching the post.</li>
<li>In your watched posts list you can see all posts that you are watching. You can also see the number of comments and how many of those are new (not yet read by you).</li>
<li>If the site owner has enabled email notifications, next to each post is an icon that tells you whether changes to this post results in notifications by email. Click the icon to enable/disable email notifications. Emails are sent to the email address you have entered in your site account details.</li>
<li>To stop watching posts, either click the trash can icon next to the post in your watched posts list or click the link by the post.</li>
</ul>


<h3>Settings</h3>
<p>The settings page allows you to customize how and when posts are watched and notifications are sent. The site owner may have set default settings that will apply to all users who have not modified their settings. The default settings will apply until you have modified your settings.</p>
<dl>
<dt>Email Notification Settings</dt>
<dd>The email notification settings allow you to change your preferences regarding how and when email notifications are sent.
  <dl>
    <dt>Automatically enable email notifications for posts that I start watching</dt>
    <dd>When you start watching a post, email notifications are automatically enabled for it as well. You can disable email notifications by clicking the email icon in your watched posts list.</dd>
    <dt>Send email notification when a post is updated or edited</dt>
    <dd>An email notification is sent when the post is changed, either by its original author or someone else. This is useful for being notified about changes to posts that many users may edit. You will not be notified if you edited the post yourself.</dd>
    <dt>Send email notification when a post receives a new comment</dt>
    <dd>An email notification will be sent when a new comment is posted to the post you're watching. You will not be notified about comments you have made yourself.</dd>
  </dl>
</dd>
<dt>Watch Automatically</dt>
<dd>These settings control when posts are watched automatically without you having to manually click "watch this post".
  <dl>
    <dt>Automatically watch posts that I comment on</dt>
    <dd>When you post a comment on a post, the post will be added to your watched posts list without you having to manually add it. This is handy if you always want to watch all posts you have commented on and discuss with other users.</dd>
    <dt>Automatically watch posts that I make</dt>
    <dd>Posts that you have made will automatically be added to your watched posts list. This helps you keep track of what changes are made to your posts and the comments that are posted about your posts.</dd>
  </dl>
</dd>
<dt>Sharing</dt>
<dd>Your list of watched posts can be shared with other users.
  <dl>
    <dt>Share my list of watched posts</dt>
    <dd>If you want other users to be able see what posts you watch, enable this setting. When you're sharing your list, a link will appear in your profile allowing other users to browse your list of watched posts.</dd>
  </dl>
</dd>
</dl>
EOT;

  //'//fix for phpeclipse syntax highlighter bug
  $templates['watcher_help_text'] .= '</div>';

  /////////////////////////////////////////////////////////////

  return $templates;

}

/**
 * Return the number of comments and new comments for a node
 *
 * @param $node A node object
 */
function _watcher_binder_comment_count($node) {
  $comments = 0;
  if ($node->comment_count) {
    $comments = $node->comment_count;

    if ($new = comment_num_new($node->nid)) {
      $comments .= '<br />';
      $comments .= l(format_plural($new, '1 new', '@count new'), "node/$node->nid", array('fragment' => 'new'));
    }
  }
  return $comments;
}


/**
 * Return a message for an updated node with tokens replaced
 *
 * @param $object
 *    A node object
 */
function _watcher_email_notifications_replace_tokens_node_update($node) {
  // Load notification message templates
  $templates = _watcher_admin_settings_field_value_templates();

  // Get message template from settings
  $message = variable_get('watcher_notifications_templates_body_node', $templates['body_node_update']) ."\r\n";

  // Start by replacing our own tokens
  $node_url = url('node/'. $node->nid, array('absolute' => true));

  $tokens = array('[node-url]');
  $replaces = array($node_url);

  $message = str_replace($tokens, $replaces, $message);

  // Replace tokens supplied by Token Module
  $message = token_replace($message, 'node', $node);

  return $message;
}

/**
 * Return a message for a new comment with tokens replaced
 */
function _watcher_email_notifications_replace_tokens_comment_insert($comment) {
  // Cache excerpt since it doesn't vary and requires regular expressions
  // to produce - thus may be expensive if there are many recipients
  static $comment_excerpt;

  // Cache node object, node_load() caches too under certain conditions,
  // but we cache regardless of circumstances
  static $node;

  // Load notification message templates
  $templates = _watcher_admin_settings_field_value_templates();

  // Get message template from settings
  $message = variable_get('watcher_notifications_templates_body_cmt', $templates['body_comment_insert']) ."\r\n";

  // Load the node the comment is about, cache it to save queries
  if (!isset($node)) {
    $node = node_load($comment['nid']);
  }

  // Start by replacing our own tokens

  // Load excerpt from cached static
  if (!isset($comment_excerpt)) {
    $comment_excerpt = _watcher_email_notifications_create_excerpt($comment['comment']);
  }
  $comment_url = url('node/'. $comment['nid'], array('fragment' => 'comment-'. $comment['cid'], 'absolute' => true));
  $comment_reply_url = url('comment/reply/'. $comment['nid'] .'/'. $comment['cid'], array('absolute' => true));
  $node_url = url('node/'. $comment['nid'], array('absolute' => true));

  $tokens = array('[comment-excerpt]', '[comment-url]', '[comment-reply-url]', '[node-url]');
  $replaces = array($comment_excerpt, $comment_url, $comment_reply_url, $node_url);

  $message = str_replace($tokens, $replaces, $message);

  // Replace tokens supplied by Token Module
  $message = token_replace($message, 'comment', $comment);
  $message = token_replace($message, 'node', $node);

  return $message;
}

/**
 * Return a message for a confirmation email to an anonymous user
 */
function _watcher_email_notifications_replace_tokens_anonymous_watch_confirm($node, $recipient) {

  // Load notification message templates
  $templates = _watcher_admin_settings_field_value_templates();

  // Get message template from settings
  $message = variable_get('watcher_notifications_templates_body_confirm', $templates['body_confirm']) ."\r\n";

  // Start by replacing our own tokens
  $node_url = url('node/'. $node->nid, array('absolute' => true));
  $tokens = array('[node-url]');
  $replaces = array($node_url);
  $message = str_replace($tokens, $replaces, $message);

  // Replace tokens supplied by Token Module
  $message = token_replace($message);

  return $message;
}

/**
 * Return a message header or footer
 */
function _watcher_email_notifications_replace_tokens_header_and_footer($recipient, $nid, $type = 'header') {
  // Load notification message templates
  $templates = _watcher_admin_settings_field_value_templates();

  // Get header/footer template from settings
  $message = variable_get("watcher_notifications_templates_$type", $templates[$type]);

  // Set recipient email address
  $r_email = ( $recipient->mail ? $recipient->mail : ( $recipient->umail ? $recipient->umail : $recipient->wnmail ));

  $link_query = "token=". _watcher_get_token($nid, $recipient->uid);

  // Start by replacing our own tokens
  $recipient_username = $recipient->name;
  $recipient_user_settings_url = url('user/'. $recipient->uid .'/watcher/settings', array('absolute' => true));
  $stop_watching_url = url('user/'. $recipient->uid .'/watcher/toggle/'. $nid . ( $recipient->uid ? '' : '/unwatch/'. $r_email ), array('absolute' => true, 'query' => $link_query));
  $stop_watching_all_url = url('user/'. $recipient->uid .'/watcher/toggle/'. $nid .'/unwatch-all/'. $r_email, array('absolute' => true, 'query' => $link_query));

  $tokens = array('[recipient-username]', '[recipient-user-settings-url]', '[stop-watching-url]', '[stop-watching-all-url]', '[user-ip]');
  $replaces = array($recipient_username, $recipient_user_settings_url, $stop_watching_url, $stop_watching_all_url, $recipient->ip);

  $message = str_replace($tokens, $replaces, $message);

  // Replace tokens supplied by Token Module
  $message = token_replace($message);

  return $message;
}

/**
 * Create an excerpt from a string disregarding words, just plain truncation
 *
 * @param $text
 *     The text we want to create an excerpt from
 */
function _watcher_email_notifications_create_excerpt($text) {

  // Load current excerpt length setting
  $set = variable_get('watcher_email_notification_comment_excerpt_len', '200chars');

  // Strip tags since we don't want HTML emails
  $text = strip_tags($text);

  // Filter XSS, filter out potentially dangerous stuff
  $text = filter_xss($text);

  // Get text's length
  $textlen = strlen($text);

  // Determine the type of setting (percent or absolute) and calculate excerpt length
  $matches = array();
  if (preg_match('/^(.*)chars$/', $set, $matches)) {
    $len = $matches[1];
  }
  elseif (preg_match('/^(.*)percent$/', $set, $matches)) {
    $len = $textlen * ( $matches[1] / 100 );
    $len = round($len);
  }
  elseif ($set == 'nothing') {
    $len = 0;
  }
  elseif ($set == 'entire') {
    $len = $textlen;
  }

  // Create an excerpt
  if ($len > 0) {
    return (substr($text, 0, $len));
  }

  return null;

}

/**
 * Determine whether this node is of one of the node types for which Watcher is enabled
 *
 * @param $n A node identifier, eiher a NID (int) or a node object
 */
function _watcher_node_type_enabled($n) {
  // Cache node type status
  static $nodes;
  if (is_numeric($n)) {
    if (!isset($nodes[$n])) {
      $nodes[$n] = node_load($n);
    }
    $node = $nodes[$n];
  }
  elseif (isset($n->nid) && isset($n->type)) {
    if (!isset($nodes[$n->nid])) {
      $nodes[$n->nid] = $n;
    }
    $node = $nodes[$n->nid];
  }

  if ($node) {
    // Load enabled node types for Watcher
    $enabled_node_types = variable_get('watcher_content_types', array());

    // Return match
    return (bool) $enabled_node_types[$node->type];
  }
  return false;
}

/**
 * Add a message to the notification message queue
 *
 * @param $recipient
 *    An object with properties uid, name and mail
 * @param $subject
 *    Message subject
 * @param $message
 *    The contents of the email message
 */
function _watcher_email_notifications_add_to_queue($recipient, $subject, $message) {
  // $recipient is an object, obtain variable values
  $r_uid = $recipient->uid;
  // Set mail. Anonymous users have their emails stored in wnmail, registered in umail
  $r_email = ( $recipient->mail ? $recipient->mail : ( $recipient->umail ? $recipient->umail : $recipient->wnmail ));

  // Add to database
  return (_watcher_db_insert_users_notification_queue($r_uid, $r_email, $subject, $message));
}

/**
 * Send all messages in queue using the instantaneous method
 */
function _watcher_email_notifications_send_instant() {

  // If there are messages to send, proceed to sending them
  if (_watcher_static('messages to send')) {
    $method = variable_get('watcher_email_notifications_method', 'cron');
    if ($method == 'instant') {
      return _watcher_email_notifications_send_queue();
    }
  }

}

/**
 * Send all messages in queue using the cron method
 */
function _watcher_email_notifications_send_cron() {
  $method = variable_get('watcher_email_notifications_method', 'cron');
  if ($method == 'cron') {
    return _watcher_email_notifications_send_queue();
  }
}

/**
 * Send all messages in queue
 */
function _watcher_email_notifications_send_queue() {

  // Obtain current queue
  $qmsgs = _watcher_db_get_users_notification_queue();

  // Normally, we should have an array of notifications but let's check
  // just to be sure
  if (is_array($qmsgs)) {

    // Start the timer
    _watcher_email_notifications_send_queue_timer(true);

    // Process queue until timer says stop (returns false) or we run out of notifications
    while ((list($k, $qmsg) = each($qmsgs)) && _watcher_email_notifications_send_queue_timer()) {

      // Send message and add it to the sent messages array
      if (_watcher_email_notifications_send_message($qmsg)) {
        $sent_qmsgs[$qmsg->qid] = $qmsg->qid;
      }

    }

    // Purge sent messages from queue
    if (count($sent_qmsgs)) {
      _watcher_db_delete_users_notification_queue($sent_qmsgs);
    }

    // Make Watchdog entry
    $method = variable_get('watcher_email_notifications_method', 'cron');
    if ($sent_qmsgs) {
      watchdog('Watcher', 'Watcher has successfully sent !notifications using the !method method.', array('!notifications' => format_plural(count($sent_qmsgs), '1 notification', '@count notifications'), '!method' => $method));
    }
    else {
      watchdog('Watcher', 'Watcher failed to send notifications using the !method method.', array('!method' => $method), WATCHDOG_ERROR);
    }

  }

  return (bool) $sent_qmsgs;

}

/**
 * Start the timer we use when sending messages
 *
 * @param $start boolean
 *    A boolean denoting that the timer should start
 *
 * @return
 *    A boolean true if the time limit hasn't expired, a false if it has
 */
function _watcher_email_notifications_send_queue_timer($start = false) {
  // Store current time in a static var
  static $start_time;
  static $time_limit;

  // Get time limit from settings
  if (!$time_limit) {
    $time_limit = variable_get('watcher_email_notifications_send_time_limit', 5);

    // Time limit may be a string, if so it's "unlimited" so set it high
    if (!is_numeric($time_limit)) {
      $time_limit = 99999999999999;
    }
    $time_limit = (float) $time_limit;
  }

  // Get current time in seconds and microseconds
  list($msec, $sec) = explode(" ", microtime(true));
  $cur_time = ((float)$msec + (float)$sec);

  // We're starting, set starting time
  if ($start) {
    $start_time = $cur_time;
  }
  // Checked whether the timer has expired
  else {
    $elapsed_time = $cur_time - $start_time;
    return ($elapsed_time < $time_limit);
  }
}

/**
 * Send a message
 *
 * @param $qmsg
 *    An object containing a queue item with properties qid, uid, mail, subject and message
 */
function _watcher_email_notifications_send_message($qmsg) {

  // Cache 'from' header
  static $from;

  // Set up addresses
  $site_name = check_plain(variable_get('site_name', ''));
  $site_mail = variable_get('site_mail', ini_get('sendmail_from'));

  // No need to generate the from header more than once
  if (!isset($from)) {
    $from = sprintf('"%s" <%s>', mime_header_encode(_watcher_filter_rfc2047_especials($site_name)), $site_mail);
  }

  // Subject
  // Decode html entities, quotes in particular so we don't get HTML character
  // entity tags in our emails, finally strip RFC 2047 especials
  $subject = html_entity_decode(strip_tags(filter_xss($qmsg->subject)), ENT_QUOTES);

  // To
  $to = $qmsg->mail;

  // Body
  $body = html_entity_decode(strip_tags(filter_xss($qmsg->message)), ENT_QUOTES);

  // Key
  $key = 'watcher#'. $qmsg->qid;

  // Set up params
  $params['subject'] = $subject;
  $params['body'] = $body;

  // Language
  $language = language_default();

  // Send email
  $message = drupal_mail('watcher', 'notification', $to, $language, $params, $from, true);

  return $message['result'];

}

/**
 * Displays a form with an email address field
 *
 * @param $nid
 *     A node id NID
 * @param $watch_action
 *     The watch action to be carried out: watch or unwatch
 *
 * @return
 *     A FAPI form array
 */
function _watcher_watch_toggle_anonymous_form(&$form_state, $nid, $watch_action){

  // Load previous entered form details from current session
  $mail = _watcher_session('mail');
  $what = _watcher_session('what');

  $node = node_load($nid);

  // Load the mail footer template
  $templates = _watcher_admin_settings_field_value_templates();
  $message = variable_get("watcher_notifications_templates_$type", $templates['footer_confirm']);

  // Determine if user's IP address token is included in template
  $ip_reported = (strpos($message, '[user-ip]') !== false);

  // Messages
  $ip_info = ' '. t("The confirmation message contains your IP address so that in case of abuse the owner of the email account can identify who submitted the form.");

  $form = array();

  // Help text depending on watch action
  if ($watch_action == 'unwatch') {
    $form['watcher'] = array (
      '#type' => 'fieldset',
      '#title' => '<strong>'. t("Stop watching %title", array('%title' => check_plain($node->title))) .'</strong>',
    );
    $info = t("Enter your email address below to stop watching %title. The form will remember the email you enter until next time.", array('%title' => check_plain($node->title), '%ipinfo' => ( $ip_reported ? $ip_info : null )));
  }
  else {
    $form['watcher'] = array (
      '#type' => 'fieldset',
      '#title' => '<strong>'. t("Watch %title", array('%title' => check_plain($node->title))) .'</strong>',
    );
    $info = t("Enter your email address below to start watching %title. The form will remember the email you enter until next time. Once you submit the form, a confirmation message will be sent to the email address entered. %ipinfo", array('%title' => $node->title, '%ipinfo' => ( $ip_reported ? $ip_info : null )));
  }

  $form['watcher'][] = array(
    '#value' => $info,
    '#weight' => 0,
  );

  $form['watcher']['mail'] = array(
    '#type' => 'textfield',
    '#title' => t('Your email address'),
    '#default_value' => $mail,
    '#size' => 32,
    '#maxlength' => 128,
    '#required' => true,
    '#weight' => 5,
  );

  // If the user wants to start watching, present what to watch for
  if ($watch_action == 'watch') {
    $form['watcher']['what'] = array(
      '#type' => 'select',
      '#title' => t('What to watch for'),
      '#options' => array(
        'all' => t('Updates and new comments'),
        'comments' => t('Only new comments'),
        'updates' => t('Only updates'),
    ),
      '#default_value' => $what,
      '#description' => t('Choose to be informed about new comments, updates (this post being changed) or both.'),
      '#required' => true,
      '#weight' => 10,
    );
  }

  $form['watcher']['submit'] = array(
    '#type' => 'submit',
    '#value' => ( $watch_action == 'watch' ? t('Watch') : t('Unwatch') ),
    '#weight' => 15,
  );

  $form['watcher']['nid'] = array(
    '#type' => 'hidden',
    '#value' => $nid
  );

  $form['watcher']['watch_action'] = array(
    '#type' => 'hidden',
    '#value' => $watch_action
  );

  return $form;
}

/**
 * Stores a value in a static variable
 *
 * @param $name
 *     The name of the value to store
 * @param $value
 *     The value to store, if omitted stored value for $name is returned
 * @return
 *     If $value is not passed, the value stored for $name is returned
 */
function _watcher_static($name, $value = null) {
  static $static;
  if (is_null($value)) {
    return $static[$name];
  }
  $static[$name] = $value;
}

/**
 * Removes especials in RFC2047 from string
 *
 * @param $string
 *     String to be processed
 * @return
 *     A string without RFC2047 especials
 */
function _watcher_filter_rfc2047_especials($string) {
  $especials = array('(', ')', '<', '>', '@', ',', ';', ':', '/', '[', ']', '?', '.', '=');
  return str_replace($especials, '', $string);
}

/**
 * Store and retrieve variables from session
 *
 * @param $key
 *     Key to be set or got
 * @param $value
 *     Value to set for $key
 * @return
 *     If only $key is given, sought-after value for key
 */
function _watcher_session($key, $value = null) {
  if (isset($value)) {
    unset($_SESSION[$key]);
    $_SESSION[$key] = $value;
  }

  if (isset($_SESSION[$key])) {
    return $_SESSION[$key];
  }
}


/***************************************************************************************
 * SUBMIT HANDLERS
 ***************************************************************************************/

/**
 * Handle user settings form submit
 *
 * @param $form
 *     A form array
 * @param $form_state
 *     A form state array
 */
function _watcher_user_settings_submit($form, &$form_state) {

  // Extract settings
  foreach ($form_state['values'] as $key => $item) {
    if (preg_match('/^watcher_(.*)/', $key, $matches)) {
      $setting_key = $matches[1];
      $tosave[$setting_key] = $item;
    }
  }

  _watcher_user_settings_save($tosave, $form_state['values']['uid']);
}

/**
 * Handle anonymous watch toggle form submit
 *
 * @param $form
 *     A form array
 * @param $form_state
 *     A form state array
 */
function _watcher_watch_toggle_anonymous_form_submit($form, &$form_state) {
  global $user;
  $nid = $form_state['values']['nid'];
  $what = $form_state['values']['what'];
  $watch_action = $form_state['values']['watch_action'];
  $node = node_load($nid);

  // Get user's IP, even when behind proxy/cache server
  if (!($user->ip = $_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $user->ip = $_SERVER['REMOTE_ADDR'];
  }

  // Store form details in session for use later
  _watcher_session('mail', $form_state['values']['mail']);
  _watcher_session('what', $form_state['values']['what']);

  // Add user's email to $user object
  $user->mail = $form_state['values']['mail'];

  // Get destination for redirection later on
  $dest = drupal_get_destination();

  // Handle watching/unwatching
  if ($watch_action == 'unwatch') {
    if ($user->mail && _watcher_user_unset_watching_nodes(array($nid), $user)) {
      $message = t('You are no longer watching %ntitle. <strong><a href="!undo">Undo</a></strong>', array('%ntitle' => check_plain($node->title), '!undo' => url("user/$user->uid/watcher/toggle/$node->nid", array('query' => $dest))));
      drupal_set_message($message);

      // Remove NID from session to mark it as no longer being watched
      $nids = _watcher_session('watching');
      if (is_array($nids)) {
        unset($nids[$nid]);
        _watcher_session('watching', $nids);
      }
    }
    else {
      $message = t('That email address is not associated with any user watching %ntitle', array('%ntitle' => check_plain($node->title)));
      drupal_set_message($message);
    }
  }
  else {
    if (_watcher_user_set_watching_node($nid, $user, $what)) {

      // Adapt message depending on what the user is watching for
      $message_what['all'] = t('updates and new comments');
      $message_what['comments'] = t('new comments');
      $message_what['updates'] = t('updates');

      $message = t('You are now watching %ntitle for !what. A message will be sent to the email address you entered (%email) to confirm this action.', array('%ntitle' => $node->title, '%email' => $user->mail, '!what' => $message_what[$what]));
      drupal_set_message($message);

      // Store node's NID in session to mark it as being watched
      if (!is_array($nids = _watcher_session('watching'))) {
        $nids = array();
      }
      $nids[$nid] = $nid;
      _watcher_session('watching', $nids);

      // Send confirmation message
      _watcher_email_notify_anonymous_watch_confirm($user, $nid, $what);
    }
  }

  // Redirect user back to originating page
  drupal_goto();
}

/**
 * Get a private token used to protect links from spoofing - CSRF.
 */
function _watcher_get_token($seed, $uid = false) {
  if ($uid === false) {
    $uid = $GLOBALS['user']->uid;
  }
  return drupal_get_token($seed . $uid);
}

/**
 * Check to see if a token value matches the specified node.
 */
function _watcher_check_token($token, $seed, $uid = false) {
  if ($uid === false) {
    $uid =  $GLOBALS['user']->uid;
  }
  return drupal_get_token($seed . $uid) == $token;
}

/**
 * FORM VALIDATORS
 */

/**
 * Validate an anonymous user watch toggle form
 *
 * @param $form
 *     Structured FAPI form
 * @param $form_state
 *     Populated form with values
 */
function _watcher_watch_toggle_anonymous_form_validate($form, &$form_state) {
  if (!valid_email_address(trim($form_state['values']['mail']))) {
    form_set_error('mail', t('You must enter a valid e-mail address.'));
  }
}

// EOF
