Thanks for this wonderful module. So, so useful!

However, my client wants to be able to track downloads per user, so I've adapted the module code in places, and wanted to submit it back to the module repository for inspection. I'm fairly happy mucking about elbow deep in the Drupal API, but I'm not a coder by training and not sure about creating/applying patches and so on.

Anyway, here's my code for review with full module attached.

First off, I wanted to be able to grab the UID and the IP address for the user at the time of download (UID for registered users, IP address for anonymous users), so I've adapted download_count.install to hold that data.


function download_count_install() {
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      db_query("CREATE TABLE if not exists {file_downloads} (
        filename varchar(255) character set utf8 NOT NULL,
        count int(11) NOT NULL default '0',
		timestamp int(11) NOT NULL default '0',
		`uid` int(11) default NULL,
		`hostname` varchar(15) NOT NULL default '',
		UNIQUE KEY filename (filename)
		) /*!40100 DEFAULT CHARACTER SET utf8 */");
      break;
    case 'pgsql':
	  break;
  }
}

Then, to collect that data on download, I've added some elements to the function download_count_file_download, first by making the $user var global then storing the data in the database, checking to see if the user has previously downloaded the given file …

/**
 * Implementation of hook_file_download()
 */
function download_count_file_download($filename) {

  global $user;

  $extensions = explode(' ', trim(variable_get('download_count_excluded_file_extensions', '')));
  if (count($extensions)) {
    if (in_array(substr($filename, strpos($filename, '.') + 1), $extensions)) {
      return;
	}
  }

  $file = file_create_path($filename);
  $result = db_query("SELECT f.* FROM {files} f WHERE filepath = '%s'", $file);
  if ($file = db_fetch_object($result)) {
    if (user_access('view uploaded files') && node_access('view', node_load($file->nid))) { 
      $message = t('%file was downloaded', array('%file' => $filename));
      watchdog('download', $message, WATCHDOG_NOTICE);		
      // If the file is already added, just increment the count, 
      // otherwise add the file with count 1
      if(db_result(db_query("SELECT filename FROM {file_downloads} WHERE filename = '%s' AND uid = '$user->uid'", $filename))) {
        db_query("UPDATE {file_downloads} SET count = count+1, timestamp = %d  WHERE filename = '%s' AND uid = '$user->uid'", time(), $filename);
      } 
	  else {
        db_query("INSERT INTO {file_downloads} (filename, count, timestamp, uid, hostname) VALUES ('%s', 1,%d, '$user->uid', '$user->hostname')", $filename, time());
	  }			
	} 
	else {
	  $message = t('Failed to download %file', array('%file' => $filename)); 	
      watchdog('download', $message, WATCHDOG_WARNING);
	}	
  }
}

Then the final step was to display the data in the download count display page function download_count_view_page …

function download_count_view_page() {

  global $user;

  drupal_set_title(variable_get('download_counter_view_page_title', t('Download counter')));

  $header[] = array('data' => t('filename'), 'field' => 'filename');
  $header[] = array('data' => t('hits'), 'field' => 'count', 'sort' => 'desc');
  $header[] = array('data' => t('last download'), 'field' => 'timestamp');
  $header[] = array('data' => t('user'), 'field' => 'uid');
  $header[] = array('data' => t('IP address'), 'field' => 'timestamp');
  $header[] = array('data' => t('action'));
  
  $rows = array();

  if(user_access('view all downloads count')) {
	  $result = db_query("SELECT fd.filename, fd.count, fd.timestamp, fd.uid, fd.hostname, f.nid, n.type FROM {file_downloads} fd JOIN {files} f ON fd.filename = f.filename JOIN {node} n ON n.nid = f.nid"  . tablesort_sql($header));
	}
	else {
	  $result = db_query("SELECT fd.filename, fd.count, fd.timestamp, fd.uid, fd.hostname, f.nid, n.type FROM {file_downloads} fd JOIN {files} f ON fd.filename = f.filename JOIN {node} n ON n.nid = f.nid WHERE n.uid = %d" . tablesort_sql($header), $user->uid);
	}
	
  while ($file = db_fetch_object($result)) {
    $row = array();
    $row[] = $file->filename;
	$row[] = $file->count;	
	$row[] = format_interval(time() - $file->timestamp) . ' ago';	
  	$user = user_load(array(uid=>$file->uid));
	$row[] = l($user->name, 'user/'.$user->uid);
	$row[] = $file->hostname;
    $row[] = l(t('view ' . $file->type), 'node/' . $file->nid);
	$rows[] = $row;	
  }

  if (empty($rows)) {
    $rows[] = array(array('data' => t('No file attachment has been downloaded.'), 'colspan' => '3'));
  }

  $output = check_markup(variable_get('download_counter_view_page_header', ''), 
                   variable_get('download_counter_view_page_format', 0), 
				   false);

  $output .= theme('table', $header, $rows, array('class' => 'download_count'));
  
  $output .= check_markup(variable_get('download_counter_view_page_footer', ''), 
                   variable_get('download_counter_view_page_format', 0), 
				   false);
  
  return $output;
}

So, module file attached. How does it look?

CommentFileSizeAuthor
#14 download_log.zip1.73 KBBudrick
download_count.txt11.19 KBjoe-b

Comments

Chill35’s picture

I understand that your client would want to know who downloaded what, and preferably in some sort of table. But I don't understand why he would want to count the number of times a file was downloaded by the same person...?

At first glance there seems to be a problem with how downloads are counted for anonymous users. The anonymous users are considered like one person :

 if(db_result(db_query("SELECT filename FROM {file_downloads} WHERE filename = '%s' AND uid = '$user->uid'", $filename))) {
        db_query("UPDATE {file_downloads} SET count = count+1, timestamp = %d  WHERE filename = '%s' AND uid = '$user->uid'", time(), $filename);
      } 

Is that on purpose ?

Nitpicking (really is no big deal) :

$rows[] = array(array('data' => t('No file attachment has been downloaded.'), 'colspan' => '3'));

We now have 5 columns, so the first column has to stretch to 4 extra columns, so we need :

$rows[] = array(array('data' => t('No file attachment has been downloaded.'), 'colspan' => '5'));

And it's preferable (more secure) to use placeholders for values in mysql INSERT queries :

db_query("INSERT INTO {file_downloads} (filename, count, timestamp, uid, hostname) VALUES ('%s', 1, %d, '$user->uid', '$user->hostname')", $filename, time());

... should be :

db_query("INSERT INTO {file_downloads} (filename, count, timestamp, uid, hostname) VALUES ('%s', 1, %d, %d, '%s')", $filename, time(), $user->uid, $user->hostname);

joe-b’s picture

Re: anonymous users are considered like one person. Is that on purpose ?

yes, it's laziness, i suppose - in this site only registered users have permissions to download files, so anonymous users will never need to be accounted for. in a bit of a rush to get the job done and too desperate to complete the adaptation for universal circumstances. i will need that for other sites so i will try to complete it soon when it's not so frenetic. i know it's only a couple of lines, but that's just the way it is for the moment!

But I don't understand why he would want to count the number of times a file was downloaded by the same person...?

it's because the client has got funding to put the website together and needs complete management data to justify the funding or else give the money (my fees!) back.

Chill35’s picture

In the case where we can safely ignore anonymous users because they can't 'view uploaded files' this code will work. It will give you a different line for each user-who-downloaded-it/file combination. Which can be extremely useful.

I probably should do something along these lines too.

Thanks!

joe-b’s picture

Thanks so much for pointers on code - very helpful!

rallycivic’s picture

I've just used this on a site, and found a couple of things to change.

I needed to get rid of "UNIQUE KEY filename (filename)" for the table definition, as we need more than one entry per filename now.

A big gotcha was overwriting the $user global when displaying the download_counter page! This leaves the viewer with the identity of the
user who download the last file in the list...

I changed this:
$user = user_load(array(uid=>$file->uid));
$row[] = l($user->name, 'user/'.$user->uid);
To this:
$fileuser = user_load(array(uid=>$file->uid));
$row[] = l($fileuser->name, 'user/'.$fileuser->uid);

Download counting with users was just what I wanted - so thanks!

budda’s picture

Just adding myself here to track the progress of this getting in to CVS.

Dr_Tom’s picture

This works a treat, although I did need to change the code of the install as I already had the (unaltered) module working and therefore experienced probems changing the database (basically told it to add a couple of columns to the 'file_downloads' table, rather than 'CREATE TABLE if not exists'). Anyway, a big thumbs up!

maxrisc’s picture

I had to do some manual hacking on this as I had managed to screw up download_count about a month ago and just got around to fixing it when I saw this.

I manually created the table and made the changes to the files and it works very nicely.

Thank you for your work!

mrtoner’s picture

I wonder if we might see this rolled into the module? To answer the question:

But I don't understand why he would want to count the number of times a file was downloaded by the same person...?

one might use this to flag users who have shared their login credentials on a membership site.

Hvr’s picture

I would be very happy if I could see which users who downloads specific files.

vj0914’s picture

Thank you so much, I was looking for this. It is working perfectly on my site.

balsama’s picture

Any idea what it would take to get this working on Drupal 6? Or is anyone currently working on that?

It's a great feature.

thanks

upupax’s picture

subscribe.
I was looking for the same feature.

Budrick’s picture

StatusFileSize
new1.73 KB

I did almost the same recently.

"Download log" module logs who, when downloaded which file and displays this stats in a table which is available by "Download log" link in admin area.

trinin’s picture

Thank you! you just saved my job!

upupax’s picture

thanks! does it have an official project page?

profjk’s picture

Thanks for this.

abi’s picture

any one have idea hoe to make this as view ? if it will be available as view then it will be big plus and even work in drupal 6 .

Abi

stopbox’s picture

subcribing

stopbox’s picture

subscribing

Chill35’s picture

The download_count module logs the following information each time there's a download:

The User Object of whoever downloaded the file.
The referer: The page that referred the user to the page where the download occurred.
The time: The UNIX timetamp of the date/time the download occurred.
Etc.

In Drupal 6, you can 'hook' onto the logging system. Just watch / listen to all messages that come from the download_count module. The type of these logged messages is 'download'. Do what you want with what you get. More info here: http://api.drupal.org/api/function/hook_watchdog/6

A module already uses this hook in core: syslog. Use that module's code as your guide. The sky is the limit here: you can send yourself SMS or email notifications whenever there's a download, and/or you can write stats to a .csv file.

You will have to write your own module.

function YOURMODULENAME_watchdog($entry) {
  if($entry['type'] == 'download') {
  /* You do what you have to do. */
  }
}

In Drupal 5, apply this patch to gain the same hook mechanism: http://drupal.org/node/149341

WorldFallz’s picture

Status: Needs review » Closed (won't fix)

closing due to new maintainer and an unsupported version. A new 2.x version is forthcoming (which includes this feature), please open new issues against that version when available if necessary.