tutorial

Mixing private and public downloads in Drupal 6

It's a well know fact that Drupal 6 has built in support for private downloads. For those of you that don't, enabling the private downloads feature on Administer > Settings > Site configuration puts Drupal back into control when it comes to file downloads. Enabling this option all file requests are handled via Drupal. So Drupal can do download counting, access checks, ... In the case of public downloads, Drupal never knows when a file (in the files directory) is requested, since your web server is handling these on its own.

A problem in Drupal (now) though, is that you have too choose between one or the other. By default you can't use the private downloads and public downloads method at the same time. "By default" I said, because there's a very simple way to mix both.

Suppose you want to protect some directory in your files folder so its files are only visible to users of a certain role. Let's call that directory 'privatedownloads'. Create a .htaccess file in that folder and put in the following content:

<IfModule mod_rewrite.c>
  RewriteEngine on
  RewriteBase /system/files/privatedownloads
  RewriteRule ^(.*)$ $1 [L,R=301]
</IfModule>

This will redirect all calls to this folder to a new path, system/files/privatedownloads. So a call to files/privatedownloads/test.jpg will be redirected to system/files/privatedownloads/test.jpg.

Since this path now doesn't physically exist on your server (it shouldn't), the request will be passed to Drupal. Let's respond to it by implementing hook_menu.

/**
 * Implementation of hook_menu().
 */

function your_module_name_menu() {
  $items['system/files/privatedownloads'] = array(
    'access arguments' => array('access private downloads folder'),
    'type' =>  MENU_CALLBACK,
    'page callback' => 'file_download',
    'page arguments' => array('privatedownloads'),
  );
  return $items;
}

As you see, we're passing of the request to file_download, a function that is available in Drupal core and which is normally used for Drupal's default implementation of private downloads. I've also attached a permission to this callback, called access private downloads folder. Now you can go to your permissions page and set this permission for the desired roles.

This all works fine now (we're getting the correct file and the permission are at work), only images aren't displayed as images, pdf files aren't displayed in a pdf reader etc. That's due to an incorrect mimetype. Let's set the correct mimetype using hook_file_download and we're done.

/**
 * Implementation of hook_file_download().
 */

function your_module_name_file_download($file) {
  $info = image_get_info(file_create_path($file));
  return array('Content-type: '. file_get_mimetype($file));
}

Creating your own RSS feeds with Drupal's node_feed

If you need to create your own RSS feed in Drupal, you can do this in only a few lines of code using the same function the default Drupal feeds are generated.

This function is called node_feed($nodes = 0, $channel = array()) and it expects a database query result resource generating the required node identifiers to generate the RSS code.

The following example should give you enough information to use this in your own module. In this example I will only include the nodes of type 'story' in the RSS feed.

<?php
function mymodule_rss() {
  $nodes = db_query_range(db_rewrite_sql('SELECT n.nid FROM {node} n WHERE n.type = \'story\' AND n.status = 1 ORDER BY n.created DESC'), 0, variable_get('feed_default_items', 10));
  node_feed($nodes);  
}

/**
 * Implementation of hook_menu().
 */

function mymodule_menu($may_cache) {
  if ($may_cache) {
    $items[] = array('path' => 'rss.xml', 'title' => t('RSS feed of latest stories'),
      'callback' => 'mymodule_rss',
      'access' => user_access('access content'),
      'type' => MENU_CALLBACK);
  }
  return $items;
}
?>

We can finish this by adding our feed as a syndication link in our HTML header using the Drupal core function drupal_add_feed($url = NULL, $title = '').

To achieve this in our example we alter the previously mention hook_menu implementation as follows:

<?php
/**
 * Implementation of hook_menu().
 */

function mymodule_menu($may_cache) {
  drupal_add_feed(base_path().'rss.xml', 'RSS feed of latest stories';
 
  if ($may_cache) {
    $items[] = array('path' => 'rss.xml', 'title' => t('RSS feed of latest stories'),
      'callback' => 'mymodule_rss',
      'access' => user_access('access content'),
      'type' => MENU_CALLBACK);
  }
  return $items;
}
?>

Syndicate content