<?php
/**
 * Watcher Module
 * by Jakob Persson of NodeOne <jakob@nodeone.se>
 * With ideas and feedback from Hans Dekker of Wordsy.com
 *
 * are updated or commented on.
 *
 * Sponsored by
 *   Wordsy - www.wordsy.com
 *   NodeOne - www.nodeone.se
 */

/***************************************************************************************
 * DECLARATION OF CONSTANTS
 ***************************************************************************************/

// Values for "watch_for" column in database
define('WATCHER_WATCH_FOR_ALL', 1);
define('WATCHER_WATCH_FOR_COMMENTS', 2);
define('WATCHER_WATCH_FOR_UPDATES', 3);

/***************************************************************************************
 * DATABASE FUNCTIONS
 ***************************************************************************************/

/**
 * Returns a wealth of information on the nodes a user is watching
 * Results are pageable
 *
 * @param $user
 *    User object of the user whose binder we want posts for
 * @param $header
 *    The table header array, used for table sorting
 * @return
 *    A paged database result resource
 */
function _watcher_db_get_watched_nodes_full($user = false, $header = null) {
// Taken from tracker.module
// TODO: These queries are very expensive, see http://drupal.org/node/105639
  if ($user->uid) {
    $sql = 'SELECT DISTINCT(n.nid), n.title, n.type, n.changed, n.uid, u.name, wn.send_email, wn.added, GREATEST(n.changed, l.last_comment_timestamp) AS last_updated, l.comment_count FROM {node} n INNER JOIN {node_comment_statistics} l ON n.nid = l.nid INNER JOIN {users} u ON n.uid = u.uid INNER JOIN {watcher_nodes} wn ON n.nid = wn.nid LEFT JOIN {comments} c ON n.nid = c.nid AND (c.status = %d OR c.status IS NULL) WHERE n.status = 1 AND wn.uid = %d';
    $sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n LEFT JOIN {comments} c ON n.nid = c.nid AND (c.status = %d OR c.status IS NULL) INNER JOIN {watcher_nodes} wn ON n.nid = wn.nid WHERE n.status = 1 AND wn.uid = %d AND wn.uid = %d';
    if ($header) {
      $sql .= tablesort_sql($header);
    }
    $sql = db_rewrite_sql($sql);
    $sql_count = db_rewrite_sql($sql_count);
    $result = pager_query($sql, 25, 0, $sql_count, COMMENT_PUBLISHED, $user->uid, $user->uid, $user->uid);
    return $result;
  }
}

/**
 * Returns minimal information on the nodes a user is watching
 *
 * @param $user
 *    User object of the user whose watched posts we want
 *
 * @return
 *    A database result resource
 */
function _watcher_db_get_watched_nodes_mini($user) {
  if ($user->uid) {
    $sql = 'SELECT DISTINCT(nid) FROM {watcher_nodes} WHERE uid = %d';
  }
  else {
    $sql = "SELECT DISTINCT(nid) FROM {watcher_nodes} WHERE mail = '%s'";
  }
  $result = db_query($sql, ( $user->uid ? $user->uid : $user->mail ));
  return $result;
}

/**
 * Get NIDs by UID
 * see what posts a user is watching
 *
 * @param $uid
 *     UID - a user id.
 * @return
 *     A database result set of matching table rows.
 */
function _watcher_db_get_nids_by_uid($uid) {
  $sql = 'SELECT nid FROM {watcher_nodes} WHERE uid = %d';
  $result = db_query($sql, $uid);
  while($row = db_fetch_object($result)) {
    return $result;
  }
  return false;
}

/**
 * Get UIDs by NID
 * see what users are watching a post
 *
 * @param $nid
 *     NID - a node id.
 * @return
 *     A database result set of matching table rows.
 */
function _watcher_db_get_uids_by_nid($nid) {
  $sql = 'SELECT uid FROM {watcher_nodes} WHERE nid = %d';
  $result = db_query($sql, $nid);
  while ($row = db_fetch_object($result)) {
    return $result;
  }
  return false;
}

/**
 * Get NIDs by user
 * see what posts a user is watching
 *
 * @param $user
 *     A user object
 * @return
 *     An array of NIDs with NIDs as keys and values
 */
function _watcher_db_get_nids_by_user($user) {
  if ($user->uid) {
    $sql = 'SELECT nid FROM {watcher_nodes} WHERE uid = %d';
    $result = db_query($sql, $user->uid);
  }
  else {
    $sql = "SELECT nid FROM {watcher_nodes} WHERE mail = '%s'";
    $result = db_query($sql, $user->mail);
  }

  while ($row = db_fetch_object($result)) {
    $nids[$row->nid] = $row->nid;
  }

  return $nids;
}

/**
 * Is user watching NID
 * check if a specific user is watching the given node
 *
 * @param $user
 *     A user object.
 * @param $nid
 *     A node NID
 *
 * @return
 *     A database result set of matching table rows.
 */
function _watcher_db_get_user_nid($user, $nid) {
  if (!$user->uid) {
    $sql = "SELECT mail FROM {watcher_nodes} WHERE mail = '%s' AND nid = %d";
    $result = db_query($sql, $user->mail, $nid);
  }
  else {
  $sql = 'SELECT uid FROM {watcher_nodes} WHERE uid = %d AND nid = %d';
    $result = db_query($sql, $user->uid, $nid);
  }

  while($row = db_fetch_object($result)) {
    return $result;
  }
  return false;
}

/**
 * Add NID to UID's watch list, enable email notifications if desired
 *
 * @param $nid
 *     The nid of the node the users is going to watch
 * @param $user
 *     A user object
 * @param $what
 *     A string indicating what an anonymous user watches for: all, comments, updates
 *
 * @return
 *     Boolean true on success
 */
function _watcher_db_set_nid_user($nid, $user, $what = null) {

  if (!$user->uid) {
    // If the user is anonymous, we first remove any already existing rows and replace
    // them to allow the user to alter previously set watches
    $sql = "DELETE FROM {watcher_nodes} WHERE nid = %d AND uid = %d AND mail = '%s'";
    db_query($sql, $nid, $user->uid, $user->mail);
  }

  if ($what) {
    // Mapping integers to strings
    $for['all'] = WATCHER_WATCH_FOR_ALL;
    $for['comments'] = WATCHER_WATCH_FOR_COMMENTS;
    $for['updates'] = WATCHER_WATCH_FOR_UPDATES;

    $sql = "INSERT INTO {watcher_nodes} (uid, mail, nid, added, watch_for) VALUES(%d, '%s', %d, %d, %d)";
    $res = db_query($sql, $user->uid, $user->mail, $nid, time(), $for[$what]);
  }
  else {
    $sql = "INSERT INTO {watcher_nodes} (uid, nid, added) VALUES(%d, %d, %d)";
    $res = db_query($sql, $user->uid, $nid, time());
  }

  return $res;
}

/**
 * Remove NIDs from the UID's watch list
 * @param $nids
 *     Array of node NIDs
 * @param $user
 *     A user object.
 *
 * @return
 *     A boolean indicating success/failure
 */
function _watcher_db_delete_nids_user($nids, $user) {
  if (!count($nids)) {
    return;
  }
  $sql_in = implode(',', $nids);
  if (!$user->uid) {
    $sql = "DELETE FROM {watcher_nodes} WHERE mail = '%s' AND nid IN(%s)";
    $res = db_query($sql, $user->mail, $sql_in);
  }
  else {
    $sql = 'DELETE FROM {watcher_nodes} WHERE uid = %d AND nid IN(%s)';
    $res = db_query($sql, $user->uid, $sql_in);
  }
  return (bool) db_affected_rows();
}

/**
 * Delete a node from the watch list
 * @param $nid
 *    NID of node to delete
 * @return
 *    Boolean of success or failure
 */
function _watcher_db_delete_nid($nid) {
  $sql = 'DELETE FROM {watcher_nodes} WHERE nid = %d';
  $res = db_query($sql, $nid);
  return (bool) $res;
}

/**
 * Remove all NIDs on UID's watch list
 * @param $uid
 *     UID - a user id.
 * @return
 *     A boolean indicating success/failur
 */
function _watcher_db_delete_uid($uid) {
  $sql = 'DELETE FROM {watcher_nodes} WHERE uid = %d';
  $res = db_query($sql, $uid);
  return (bool) $res;
}

/**
 * Get email notification status given UID and NID
 *
 * @param $uid
 *     UID - a user id.
 * @param $nid
 *     NID - a node id.
 * @return
 *     Boolean True if email notifications are enabled for this user and node
 */
function _watcher_db_get_send_email_status($nid, $uid) {
  $sql = 'SELECT send_email FROM {watcher_nodes} WHERE uid = %d AND nid = %d';
  $result = db_query($sql, $uid, $nid);
  $row = db_fetch_object($result);
  if ($row->send_email) return true;
  return false;
}

/**
 * Set email notification status given NID and UID
 *
 * @param $nid
 *     NID - a node id
 * @param $user
 *     A user object
 * @param $send_email
 *     A boolean integer, 1 or 0
 * @return
 *     Boolean denoting success/failure.
 */
function _watcher_db_set_send_email_status($nid, $user, $send_email = 0) {
  if ($user->uid) {
    $sql = 'UPDATE {watcher_nodes} SET send_email = %d WHERE uid = %d AND nid = %d';
    $res = db_query($sql, $send_email, $user->uid, $nid);
  }
  else {
    $sql = "UPDATE {watcher_nodes} SET send_email = %d WHERE mail = '%s' AND nid = %d";
    $res = db_query($sql, $send_email, $user->mail, $nid);
  }
  return ((bool) $res);
}

/**
 * Save user settings to database
 *
 * @param $uid
 *     A user UID
 * @param $settings
 *     An associative array of user settings
 * @return
 *     Boolean true on success
 */
function _watcher_db_set_user_settings($uid, $settings) {

  //Add custom field to mark these user settings as custom
  $settings['custom'] = 1;

  //Serialize settings
  $fields = implode(', ', array_keys($settings));
  $values = implode(', ', array_values($settings));

  // First delete existing settings
  $sql = 'DELETE FROM {watcher_user_settings} WHERE uid = %d';
  db_query($sql, $uid);

  // Insert new settings
  $sql = 'INSERT INTO {watcher_user_settings} (uid, %s) VALUES (%d, %s)';
  $res = db_query($sql, $fields, $uid, $values);
  return (bool) $res;
}

/**
 * Delete user settings for given UID
 *
 * @param $uid
 *     A user UID
 * @return
 *     Boolean True on success
 */
function _watcher_db_delete_user_settings($uid) {
  $sql = 'DELETE FROM {watcher_user_settings} WHERE uid = %d';
  $res = db_query($sql, $uid);
  return (bool) $res;
}

/**
 * Load user settings from database
 *
 * @param $uid
 *     A user UID
 * @return
 *     An assoc array of user settings
 */
function _watcher_db_get_user_settings($uid) {
  // Cache settings
  static $settings;
  if (!isset($settings)) {
    $sql = 'SELECT * FROM {watcher_user_settings} WHERE uid = %d';
    $result = db_query($sql, $uid);
    while($row = db_fetch_array($result)) {
      unset($row['uid']);
      $settings = $row;
      return $settings;
    }
    $settings = false;
  }
  return $settings;
}

/**
 * Updates the default settings for all users who haven't customized their settings
 *
 * @param $settings
 *    An associative array of settings
 * @return
 *    A boolean true on success
 */
function _watcher_db_user_settings_update_defaults($settings) {
  // Users may have registered and not had settings set yet so add rows for any users that are "missing"
  $sql = 'INSERT INTO {watcher_user_settings} (uid) SELECT u.uid FROM {users} u LEFT JOIN {watcher_user_settings} wus ON u.uid = wus.uid WHERE ISNULL(custom)';
  $res = db_query($sql);

  // If defaults are set, apply them, otherwise return
  if (is_array($settings)) {
    // Format settings array as string
    foreach ($settings as $setting => $value) {
      $insert_array[] = $setting .'='. $value;
    }
    $insert_string = implode(', ', $insert_array);

    // Update all users who haven't customized their settings yet
    $sql = 'UPDATE {watcher_user_settings} SET %s WHERE custom <> 1';
    $res = db_query($sql, $insert_string);
  }
  return (bool) $res;
}

/**
 * Has UID customized his/her user settings?
 *
 * @param $uid
 *    A user UID
 * @return
 *    A boolean true if given UID has customized his/her user settings
 */
function _watcher_db_user_settings_uid_custom($uid) {
  $sql = 'SELECT custom FROM {watcher_user_settings} WHERE uid = %d AND custom = 1';
  $res = db_query($sql, $uid);
  while($row = db_fetch_object($res)) {
    return true;
  }
  return false;
}

/**
 * Return a list of users that want to get notified about node updates for given NID
 *
 * @param $nid
 *     The NID of the node that has been updated
 * @param $uid_exclude
 *     A UID we want to exclude from our set of users, normally the user that updated the node.
 * @return
 *     An array of objects with uid as key representing users, with attributes uid, mail and name
 */
function _watcher_db_get_users_notify_node_update($nid, $uid_exclude) {
  $sql = "SELECT wn.uid, wn.mail wnmail, u.mail umail, u.name FROM {watcher_nodes} wn LEFT JOIN {watcher_user_settings} wus ON wus.uid = wn.uid LEFT JOIN {users} u ON u.uid = wus.uid WHERE nid = %d AND ((wn.uid <> 0 AND wn.uid <> %d AND notifications_updates = 1 AND send_email = 1) OR (wn.uid = 0 AND (watch_for = 1 OR watch_for = 3)))";
  $res = db_query($sql, $nid, $uid_exclude);
  $users = array();
  while ($row = db_fetch_object($res)) {
    $users[$row->uid] = $row;
  }
  return $users;
}

/**
 * Return a list of users that want to get notified about new comments for given NID
 *
 * @param $nid
 *     The nid of the node the comment belongs to
 * @param $uid_exclude
 *     A uid we want to exclude from our set of users, normally the user that wrote the comment,
 *     what's the point in being notified about your own comments, eh?
 *
 * @return
 *     An array of objects with uid as key representing users, with attributes uid, mail and name
 */
function _watcher_db_get_users_notify_comment_insert($nid, $uid_exclude) {
  $sql = 'SELECT wn.uid, wn.mail wnmail, u.mail umail, u.name FROM {watcher_nodes} wn LEFT JOIN {watcher_user_settings} wus ON wus.uid = wn.uid LEFT JOIN {users} u ON u.uid = wus.uid WHERE nid = %d AND ((wn.uid <> 0 AND wn.uid <> %d AND notifications_new_comments = 1 AND send_email = 1) OR (wn.uid = 0 AND (watch_for = 1 OR watch_for = 2)))';
  $res = db_query($sql, $nid, $uid_exclude);
  $users = array();
  while ($row = db_fetch_object($res)) {
    $users[] = $row;
  }
  return $users;
}

/**
 * Return a list of users that want to get notified about node updates or new comments
 * for a given NID
 *
 * @param $nid
 *      The nid of the node that has been updated
 * @param $uid_exclude
 *      A uid we want to exclude from our set of users, normally the user that updated the node.
 *
 * @return
 *      An array of objects with uid as key representing users, with attributes uid, mail and name
 */
function _watcher_db_get_users_notify_about_node($nid, $uid_exclude) {
  $sql = 'SELECT wn.uid, wn.mail wnmail, u.mail umail, u.name FROM {watcher_nodes} wn LEFT JOIN {users} u ON u.uid = wn.uid WHERE nid = %d AND ((wn.uid <> 0 AND wn.uid <> %d AND send_email = 1) OR (NOT ISNULL(watch_for)))';
  $res = db_query($sql, $nid, $uid_exclude);
  $users = array();
  while ($row = db_fetch_object($res)) {
    $users[$row->uid] = $row;
  }
  return $users;
}

/**
 * Add a message to the notification queue table
 *
 * @param $qid
 *     A qid (table pk) generated by db_next_id()
 * @param $r_uid
 *     Recipient uid
 * @param $r_email
 *     Recipient email address
 * @param $subject
 *     The message subject
 * @param $message
 *     Message to be sent
 * @return
 *     A database result resource
 */
function _watcher_db_insert_users_notification_queue($r_uid, $r_email, $subject, $message) {
  $sql = "INSERT INTO {watcher_notify_queue} (uid, mail, subject, message) VALUES (%d, '%s', '%s', '%s')";
  $res = db_query($sql, $r_uid, $r_email, $subject, $message);
  return (bool) $res;
}

/**
 * Delete all rows in notification queue table
 *
 * @return
 *    A boolean denoting whether any rows were deleted, false if none, true if one or more
 */
function _watcher_db_delete_users_notification_queue_all() {
  $sql = "DELETE FROM {watcher_notify_queue}";
  $res = db_query($sql);
  return (bool) $res;
}

/**
 * Get messages from the notification queue table
 */
function _watcher_db_get_users_notification_queue() {
  $sql = "SELECT * FROM {watcher_notify_queue}";
  $res = db_query($sql);
  while ($qmsg = db_fetch_object($res)) {
    $qmsgs[] = $qmsg;
  }
  return $qmsgs;
}

/**
 * Delete a set of rows from the notification queue table
 *
 * @param $rows
 *     An array of qids (queue id)
 */
function _watcher_db_delete_users_notification_queue($qids) {

  // Database managers can only handle *finite* (gulp!) SQL queries
  // so we gotta limit the fun
  $limit = 4096; //2^12 elements

  $offset = 0;
  for ($i = 1; $offset < count($qids); $i++) {
    $batch = array_slice($qids, $offset, $limit);
    $in = implode(',', $batch);
    $sql = "DELETE FROM {watcher_notify_queue} WHERE qid IN (%s)";
    db_query($sql, $in);
    $offset = (($i * $limit) - 1);
  }
}

/**
 * Statistics for Notification Queue Table
 *
 * @return
 *    An assoc array with keys num_rows and num_sent
 */
function _watcher_db_stats_notification_queue() {
  $sql = 'SELECT * FROM {watcher_notify_queue}';
  $sql2 = 'SELECT MAX(qid) FROM {watcher_notify_queue}';
  $sql3 = 'SELECT COUNT(*) FROM {watcher_notify_queue}';

  // Obtain number of rows
  $res = db_query($sql3);
  $stat['num_rows'] = db_result($res);

  // Obtain the contents of the queue table
  $res = db_query($sql);
  while ($row = db_fetch_object($res)) {

    // Load fields
    $data['qid'] = $row->qid;
    $data['uid'] = $row->uid;
    $data['mail'] = $row->mail;
    $data['subject'] = $row->subject;
    $data['message'] = '<pre>'. $row->message .'</pre>';

    // Add to data array
    $stat['data']['content'][] = $data;
  }

  // Add header
  $stat['data']['header'] = array('qid', 'uid', 'mail', 'subject', 'message');

  // Obtain the number of notifications sent - this is not 100% accurate since we cannot
  // entirely trust the increment counter
  $res2 = db_query($sql2);
  $num_sent = db_result($res2);
  $stat['num_sent'] = ( $num_sent = db_result($res2) ? $num_sent : 0 );

  if ($res) {
    return $stat;
  }
  return false;
}

/**
 * Statistics for Watched Nodes Table
 * Numbers of users that watch nodes
 *
 * @return
 *    An assoc array with key num_users
 */
function _watcher_db_stats_watched_nodes_users() {
  $sql = 'SELECT COUNT(DISTINCT(uid)) FROM {watcher_nodes} WHERE uid <> 0';
  $sql2 = 'SELECT COUNT(DISTINCT(mail)) FROM {watcher_nodes} WHERE uid = 0';

  // Obtain number of rows
  $res = db_query($sql);
  $stat['num_users_reg'] = db_result($res);

  $res2 = db_query($sql2);
  $stat['num_users_anon'] = db_result($res2);


  if ($res && $res2) return $stat;
  return false;
}

/**
 * Statistics for User Settings Table
 *
 * @return
 *    An assoc array with key num_users_with_settings, num_users and num_users_no_settings
 */
function _watcher_db_stats_user_settings() {

  // Users with custom settings
  $sql = 'SELECT COUNT(uid) FROM {watcher_user_settings} WHERE custom = 1';

  // Total number of users on the site
  $sql2 = 'SELECT COUNT(uid) FROM {users} WHERE uid <> 0';

  // Obtain number of users with settings
  $res = db_query($sql);
  $stat['num_users_with_settings'] = db_result($res);

  // Obtain number of users without setting
  $res2 = db_query($sql2);
  $stat['num_users'] = db_result($res2);
  $stat['num_users_no_settings'] = $stat['num_users'] - $stat['num_users_with_settings'];

  if ($res) return $stat;
  return false;
}

/**
 * Return the newest node
 */
function _watcher_db_get_newest_node() {
  $sql = 'SELECT nid FROM {node} ORDER BY created DESC LIMIT 0,1';
  $res = db_query($sql);
  if ($nid = db_result($res)) {
    return $nid;
  }
  return false;
}

/**
 * Return the newest comment
 */
function _watcher_db_get_newest_comment() {
  $sql = 'SELECT cid FROM {comments} ORDER BY timestamp DESC LIMIT 0,1';
  $res = db_query($sql);
  if ($cid = db_result($res)) {
    return $cid;
  }
  return false;
}
