<?php
// $Id: commentrss.module,v 1.11 2006/08/19 18:48:00 goba Exp $

function commentrss_help($section = "admin/modules#description") {
  if ($section == "admin/modules#description") {
    return t('Provides RSS feeds for comments');
  }
}

function commentrss_menu($may_cache) {
  if ($may_cache) {
    $items[] = array('path' => 'crss', 'title' => '',
      'callback' => 'commentrss_handler',
      'access' => user_access('access content'),
      'type' => MENU_CALLBACK);
  }
  else {
    drupal_add_link(array('rel' => 'alternate',
                          'type' => 'application/rss+xml',
                          'title' => t('All comments'),
                          'href' => url('crss', NULL, NULL, TRUE)));
    if (arg(0) == 'taxonomy' && arg(1) == 'vocabulary') {
      // vocabulary_list module is serving this page - http://drupal.org/project/vocabulary_list
      drupal_add_link(array('rel' => 'alternate',
                          'type' => 'application/rss+xml',
                          'title' => t('All comments'),
                          'href' => url('crss/vocab/'. arg(2), NULL, NULL, TRUE)));
    }
    elseif (arg(0) == 'taxonomy' && arg(1) == 'term') {
      drupal_add_link(array('rel' => 'alternate',
                          'type' => 'application/rss+xml',
                          'title' => t('Comments on this taxonomy term'),
                          'href' => url('crss/term/'. arg(2), NULL, NULL, TRUE)));
    }
    elseif (arg(0) == 'node' && is_numeric(arg(1))) {
      $node = node_load(arg(1));
      if ($node->nid) {
        drupal_add_link(array('rel' => 'alternate',
                              'type' => 'application/rss+xml',
                              'title' => t('Comments for %title', array('%title' => $node->title)),
                              'href' => url('crss/node/'. arg(1), NULL, NULL, TRUE)));
        drupal_add_link(array('rel' => 'alternate',
                              'type' => 'application/rss+xml',
                              'title' => t('Comments for all *%type* posts', array('%type' => $node->type)),
                              'href' => url('crss/nodetype/'. $node->type, NULL, NULL, TRUE)));
      }
    }
  }
  return $items;
}

function commentrss_handler($type = NULL) {
  if ($type) {
    if (in_array($type, array('node', 'term', 'vocab', 'nodetype'))) {
      if (call_user_func('commentrss_feed_' . $type)) {
        return;
      }
    }
  }
  else {
    if (commentrss_feed_site()) {
      return;
    }
  }
  drupal_not_found();
}

// Provides a comment feed for the complete Drupal site (ie. all nodes)
function commentrss_feed_site() {
  $items = commentrss_format_items('n.nid');
  commentrss_format_feed($items);
  return TRUE;
}

// Provides a comment feed for nodes in a vocabulary
// For the master link in the RSS feed to work, you need vocabulary_list.module!
function commentrss_feed_vocab() {
  global $base_url;
  
  $vid = arg(2);
  if (is_numeric($vid)) {
    
    $items = commentrss_format_items('DISTINCT(n.nid)', 'INNER JOIN {term_node} r ON n.nid = r.nid INNER JOIN {term_data} d ON r.tid = d.tid', 'd.vid = %d AND', $vid);
    $vocab = taxonomy_get_vocabulary($vid);
    
    // We can link to a specific page if vocabulary listing is present
    $link = (module_exist('vocabulary_list') ? url("taxonomy/vocabulary/$vid", NULL, NULL, TRUE) : $base_url);
    
    $channel = array(
      'title'       => variable_get('site_name', 'drupal') .' - '. check_plain($vocab->name) .' - '. t('Comments'),
      'link'        => $link,
      'description' => t('Comments for "%title"', array("%title" => $vocab->name)),
    );
    
    commentrss_format_feed($items, $channel);
    return TRUE;
  }
  return FALSE;
}

// Provides a comment feed for nodes associated with a term
function commentrss_feed_term() {
  $tid = arg(2);
  if (is_numeric($tid)) {
    
    $items = commentrss_format_items('DISTINCT(n.nid)', 'INNER JOIN {term_node} r ON n.nid = r.nid', 'r.tid = %d AND', $tid);
    $term = taxonomy_get_term($tid);
    
    $channel = array(
      'title'       => variable_get('site_name', 'drupal') .' - '. check_plain($term->name) .' - '. t('Comments'),
      'link'        => url("taxonomy/term/$tid", NULL, NULL, TRUE),
      'description' => t('Comments for "%title"', array("%title" => $term->name)),
    );
    
    commentrss_format_feed($items, $channel);
    return TRUE;
  }
  return FALSE;
}

// Provides a comment feed for a single node (also includes the node teaser
// as the last item, even if there are too many comments which are not
// included in the resulting RSS output)
function commentrss_feed_node() {
  global $base_url;

  $nid = arg(2);
  if (is_numeric($nid)) {
    if (!($item = node_load($nid))) {
      return FALSE;
    }

    $items = commentrss_format_items('n.nid', '', 'n.nid = %d AND', $nid);
    $link = url("node/$nid", NULL, NULL, TRUE);

    // COPIED from node_feed() start
    $item_length = variable_get('feed_item_length', 'teaser');
    if ($item_length != 'title') {
      $teaser = ($item_length == 'teaser') ? TRUE : FALSE;
      if (node_hook($item, 'view')) {
        node_invoke($item, 'view', $teaser, FALSE);
      }
      else {
        $item = node_prepare($item, $teaser);
      }
      node_invoke_nodeapi($item, 'view', $teaser, FALSE);
    }
    switch ($item_length) {
      case 'fulltext':
        $item_text = $item->body;
        break;
      case 'teaser':
        $item_text = $item->teaser;
        if ($item->readmore) {
          $item_text .= '<p>'. l(t('read more'), 'node/'. $item->nid, NULL, NULL, NULL, TRUE) .'</p>';
        }
        break;
      case 'title':
        $item_text = '';
        break;
    }
    $extra = node_invoke_nodeapi($item, 'rss item');
    $extra = array_merge($extra, array(array('key' => 'pubDate', 'value' =>  date('r', $item->created)), array('key' => 'dc:creator', 'value' => $item->name), array('key' => 'guid', 'value' => $item->nid . ' at ' . $base_url, 'attributes' => array('isPermaLink' => 'false'))));
    $namespaces = array();
    foreach ($extra as $element) {
      if ($element['namespace']) {
        $namespaces[] = $element['namespace'];
      }
    }
    $items .= format_rss_item($item->title, $link, $item_text, $extra);
    // COPIED from node_feed() end
    
    $channel = array(
      'title'       => variable_get('site_name', 'drupal') .' - '. check_plain($item->title) .' - '. t('Comments'),
      'link'        => $link,
      'description' => t('Comments for "%title"', array("%title" => $item->title)),
    );
    
    commentrss_format_feed($items, $channel, $namespaces);
    return TRUE;
  }
  return FALSE;
}

// Provides a comment feed for nodes of a certain type
function commentrss_feed_nodetype() {
  $type = arg(2);
  if (preg_match("!^[a-z0-9-]+$!", $type)) {
    
    $items = commentrss_format_items('n.nid', '', "n.type = '%s' AND", $type);
    
    $typename = check_plain(node_get_name($type));
    $channel = array(
      'title'       => variable_get('site_name', 'drupal') .' - '. $typename .' - '. t('Comments'),
      // no module provides a page for this type of listing
      //'link'        => url("nodesbytype/$type", NULL, NULL, TRUE),
      'description' => t('Comments for "%type"', array("%type" => $typename)),
    );
    
    commentrss_format_feed($items, $channel);
    return TRUE;
  }
  return FALSE;
}

// Format a comment feed with the given items and print the output
// This is a customized version of the core node_feed()
function commentrss_format_feed($items, $channel = array(), $namespaces = array()) {
  global $base_url, $locale;
  
  $channel_defaults = array(
    'version'     => '2.0',
    'title'       => variable_get('site_name', 'drupal') . ' - ' . t('Comments'),
    'link'        => $base_url,
    'description' => t('Comments'),
    'language'    => $locale
  );
  $channel = array_merge($channel_defaults, $channel);
  $namespaces = array_merge(array('xmlns:dc="http://purl.org/dc/elements/1.1/"'), $namespaces);
  
  $output = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
  $output .= '<rss version="'. $channel["version"] . '" xml:base="'. $base_url .'" ' . implode(' ', $namespaces) . ">\n";
  $output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language']);
  $output .= "</rss>\n";

  drupal_set_header('Content-Type: text/xml; charset=utf-8');
  print $output;
}

// Format RSS for comments sent in reply to the nodes selected by the given SQL selector
function commentrss_format_items($nidselector, $joins = '', $where = '', $param = NULL) {
  global $base_url;

  $items = '';

  // Build SQL query from the passed elements
  $SQL = 'SELECT '. $nidselector .', c.cid, c.subject, c.comment, c.timestamp, c.uid, c.name, c.format FROM {node} n ' . $joins . ' INNER JOIN {comments} c ON c.nid = n.nid WHERE ' . $where . ' n.status = 1 AND c.status = %d ORDER BY c.timestamp DESC';

  // Add passed query parameter; plus we are looking for published comments
  $params = isset($param) ? array($param) : array();
  $params[] = COMMENT_PUBLISHED;

  $comments = db_query_range(db_rewrite_sql($SQL), $params, COMMENT_PUBLISHED, 0, 15);
  while ($comment = db_fetch_object($comments)) {
    if ($comment->uid) {
      if ($user = user_load(array('uid' => $comment->uid))) {
        $comment->name = $user->name;
      }
    }
    if (!$comment->name) {
      $comment->name = variable_get('anonymous', 'Anonymous');
    }
    $extra = array(array('key' => 'pubDate', 'value' => date('r', $comment->timestamp)), array('key' => 'dc:creator', 'value' => $comment->name), array('key' => 'guid', 'value' => 'comment ' . $comment->cid . ' at ' . $base_url, 'attributes' => array('isPermaLink' => 'false')));
    $link = url("node/{$comment->nid}", NULL, "comment-{$comment->cid}", TRUE);
    $items .= format_rss_item($comment->subject, $link, check_markup($comment->comment, $comment->format, FALSE), $extra);
  }
  return $items;
}
