Drupal - Create an XML page by a custom module

Instead of returning the string you want to use as output, print it using code similar to the following one.

print $output;
module_invoke_all('exit');
exit();

$output is the variable containing the output you want to show.
While the module_invoke_all('exit') call is not strictly necessary, it notifies other modules that Drupal is terminating. There are modules that implement hook_exit() to do their job, and they will not work, without that call; their purpose is to write something in a Drupal variable, or in a database table, and they don't interfere with the output your are producing.

The reason why it is necessary to do this is the code used by index.php, which contains the following:

$return = menu_execute_active_handler();

// Menu status constants are integers; page content is a string.
if (is_int($return)) {
  switch ($return) {
    case MENU_NOT_FOUND:
      drupal_not_found();
      break;
    case MENU_ACCESS_DENIED:
      drupal_access_denied();
      break;
    case MENU_SITE_OFFLINE:
      drupal_site_offline();
      break;
  }
}
elseif (isset($return)) {
  // Print any value (including an empty string) except NULL or undefined:
  print theme('page', $return);
}

drupal_page_footer();

If you return a string, it is passed to theme('page'), which renders the page as you are noticing; if you return nothing, then both is_int($return) and isset($return) are FALSE, and the relative code is not executed.

The equivalent for Drupal 7 for the code I wrote for Drupal 6 would be the following one:

print $output;
drupal_exit();

drupal_exit() contains already the call to module_invoke_all('exit').


Basically all you need is hook_menu() and node_feed() to create a basic RSS feed matching the RSS 2.0 Specification.

node_feed() then ensures by calling drupal_add_http_header('Content-Type', 'application/rss+xml; charset=utf-8') the correct headers getting applied and only RSS markup getting rendered.

But if you want a really, really custom XML feed ...

... you have to write your own node_feed() function and further on your own format_rss_channel() and format_rss_item() functions. I simply pasted these three functions into a custom module, renamed them, and customized them for my own needs. Here's a simplified example:

/**
 * Implements hook_menu().
 */
function MYMODULE_menu() {

  $items = [];

  $items['custom.xml'] = [
    'page callback'   => '_custom_feed_page_callback',
    'access callback' => TRUE,
    'type'            => MENU_CALLBACK,
  ];

  return $items;
}

/**
 * Return feed for indicated content.
 */
function _custom_feed_page_callback() {

  $nids = db_select('node', 'n');
  $nids->fields('n', ['nid', 'created']);
  $nids->condition('n.status', 1);
  $nids->orderBy('n.created', 'DESC');
  $nids->range(0, 10);
  $nids = $nids->execute()->fetchCol();

  _custom_xml_node_feed($nids);
}

/**
 * Generates and prints a custom XML feed.
 */
function _custom_xml_node_feed($nids) {

  $nodes = node_load_multiple($nids);
  $items = '';
  foreach ($nodes as $node) {

    $node->link = url("node/$node->nid", ['absolute' => TRUE]);

    $items .= _custom_xml_format_rss_item($node->title, $node->link);
  }

  global $base_url;

  $channel_defaults = array(
    'title' => variable_get('site_name', 'Drupal'),
    'link' => $base_url,
  );

  $output = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\" ?>\n";
  $output .= "<!DOCTYPE CUSTOM.EVENTS SYSTEM \"custom_import.dtd\">\n";
  $output .= "<CUSTOM.EVENTS version=\"2.25\">\n";
  $output .= _custom_xml_format_rss_channel($channel_defaults['title'], $channel_defaults['link'], $items);

  drupal_add_http_header('Content-Type', 'application/rss+xml; charset=utf-8');
  print $output;
}

/**
 * Formats an XML channel.
 */
function _custom_xml_format_rss_channel($title, $link, $items) {

  $output = "  <EVENTLIST>\n";
  $output .= "    <SOURCE id=\"12345\">\n";
  $output .= '      <TITLE>' . check_plain($title) . "</TITLE>\n";
  $output .= '      <LINK>' . check_url($link) . "</LINK>\n";
  $output .= "    </SOURCE>\n\n";

  $output .= $items;
  $output .= "  </EVENTLIST>\n";
  $output .= "</CUSTOM.EVENTS>\n";

  return $output;
}

/**
 * Formats a single XML item.
 */
function _custom_xml_format_rss_item($title, $link) {

  $output = "    <EVENT>\n";
  $output .= '      <TITLE>' . check_plain($title) . "</TITLE>\n";
  $output .= '      <LINK>' . check_url($link) . "</LINK>\n";
  $output .= "    </EVENT>\n\n";

  return $output;
}

Credits: I got the idea to do it that way after reading this brilliant blog post https://www.phase2technology.com/blog/creating-rss-feeds-with-a-light-hand

Parked working example module on GitHub https://github.com/leymannx/custom_feed

Tags:

Hooks