You are here

download_count.module in Download Count 6.2

Tracks file downloads for files stored in the drupal files table using the private files setting or custom private filefield.

File

download_count.module
View source
<?php

/**
 * @file
 * Tracks file downloads for files stored in the drupal files table using the private files setting or custom private filefield.
 */

/**
 * Implementation of hook_help().
 */
function download_count_help($path, $arg) {
  switch ($path) {
    case 'admin/help#download_count':
      return '<p>' . t("Tracks file downloads for files stored in the drupal files table. Requires either the 'private' download method setting or the method for combined public and private files described at <a href=@link>http://drupal.org/node/189239</a>. Also logs a message to the watchdog table.", array(
        '@link' => url('http://drupal.org/node/189239'),
      )) . '</p>';
  }
}

/**
 * Implementation of hook_perm().
 */
function download_count_perm() {
  $perms = array(
    'view all download counts',
    'view own download counts',
    'export download counts',
  );
  return $perms;
}

/**
 * Implementation of hook_menu().
 */
function download_count_menu() {
  $items = array();
  $items['admin/settings/download_count'] = array(
    'title' => 'Download count settings',
    'description' => 'Tracks file downloads for files stored in the drupal files table.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'download_count_admin_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'includes/download_count.admin.inc',
  );
  $items['admin/settings/download_count/general'] = array(
    'title' => 'General',
    'weight' => 1,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['download_count/%download_count_entry/reset'] = array(
    'title' => 'Download Count Reset',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'download_count_reset_form',
      1,
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'includes/download_count.pages.inc',
  );
  $items['download_count/%download_count_entry/export'] = array(
    'title' => 'Download Count Export CSV',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'download_count_export_form',
      1,
    ),
    'access arguments' => array(
      'export download counts',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'includes/download_count.export.inc',
  );
  $items['download_count'] = array(
    'title' => 'Download Count',
    'page callback' => 'download_count_view_page',
    'page arguments' => array(
      'download_count',
    ),
    'access arguments' => array(
      'view all download counts',
    ),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'includes/download_count.pages.inc',
  );
  $items['my_download_count'] = array(
    'title' => 'My Download Count',
    'page callback' => 'download_count_view_page',
    'page arguments' => array(
      'my_download_count',
    ),
    'access arguments' => array(
      'view own download counts',
    ),
    'type' => MENU_NORMAL_ITEM,
    'file' => '/includes/download_count.pages.inc',
  );
  return $items;
}

/**
 * Menu wildcard loader.
 */
function download_count_entry_load($dcid) {
  return $dcid == 'all' ? $dcid : db_fetch_array(db_query('SELECT dc.dcid, dc.fid, dc.nid, dc.uid, dc.ip_address, dc.referrer, dc.timestamp, f.filename, f.filesize FROM {download_count} dc JOIN {files} f ON dc.fid = f.fid WHERE dcid = %d', $dcid));
}

/**
 * Implementation of hook_nodeapi().
 */
function download_count_nodeapi(&$node, $op, $teaser) {
  $result = array();
  switch ($op) {
    case 'view':
      if (isset($node->files) && count($node->files) && user_access('view uploaded files') && !$teaser) {
        global $user;
        if (user_access('view own download counts') && $user->uid != 1) {
          $result = db_query("SELECT dc.fid, f.filename, COUNT(dc.dcid) AS count, MAX(dc.timestamp) AS last FROM {download_count} dc JOIN {files} f ON dc.fid = f.fid WHERE dc.nid = %d AND dc.uid = %d GROUP BY dc.fid, f.filename", $node->nid, $user->uid);
        }
        elseif (user_access('view all download counts')) {
          $result = db_query("SELECT dc.fid, f.filename, COUNT(dc.dcid) AS count, MAX(dc.timestamp) AS last FROM {download_count} dc JOIN {files} f ON dc.fid = f.fid WHERE dc.nid = %d GROUP BY dc.fid, f.filename", $node->nid);
        }
        while ($download = db_fetch_object($result)) {
          $downloads[$download->filename]['count'] = $download->count;
          $downloads[$download->filename]['last'] = $download->last;
          $node->files[$download->fid]->downloads = $download->count;
        }
        if (isset($downloads)) {
          $node->content['files']['#value'] = theme('download_count_upload_attachments', $node->files, $downloads);
        }
      }
      break;
  }
}

/**
 * Implementation of hook_theme().
 */
function download_count_theme() {
  return array(
    'download_count_upload_attachments' => array(
      'arguments' => array(
        'files' => NULL,
        'downloads' => NULL,
      ),
      'file' => 'includes/download_count.theme.inc',
    ),
    'download_count_formatter_download_count' => array(
      'arguments' => array(
        'element' => NULL,
      ),
      'file' => 'includes/download_count.theme.inc',
    ),
  );
}
function download_count_field_formatter_info() {
  return array(
    'download_count' => array(
      'label' => t('Generic files with download count'),
      'field types' => array(
        'filefield',
      ),
      'multiple values' => CONTENT_HANDLE_CORE,
      'description' => t('Displays all kinds of files with an icon and a linked file description with download count information.'),
    ),
  );
}

/**
 * Implementation of hook_view_api().
 */
function download_count_views_api() {
  return array(
    'api' => 2.0,
    'path' => drupal_get_path('module', 'download_count') . '/includes',
  );
}

/**
 * Implementation of hook_file_download().
 */
function download_count_file_download($filename, $checkonly = FALSE) {

  // Special use of hook_file_download() - 2nd argument added to indicate that it is not a download, but only an access check.
  if ($checkonly) {
    return;
  }
  global $user;
  $extensions = explode(' ', drupal_strtolower(trim(variable_get('download_count_excluded_file_extensions', 'jpg jpeg gif png'))));
  if (count($extensions)) {
    $pathinfo = pathinfo($filename);
    if (in_array(drupal_strtolower($pathinfo['extension']), $extensions)) {
      return;
    }
  }
  $filepath = file_create_path($filename);

  // NULL: not known
  // FALSE: not accessible
  // TRUE: accessible
  $accessible_file = NULL;

  // check if the file is known by Upload
  $accessible_file = _download_count_is_accessible_by_upload($filepath);
  if ($accessible_file === NULL) {

    // check if the file is known by CCK FileField
    $accessible_file = _download_count_is_accessible_by_filefield($filepath);
  }
  if ($accessible_file === NULL) {

    // not known by any hooks, so we don't care about this file
    return;
  }

  // inaccessible file
  if ($accessible_file === FALSE) {
    watchdog('download_count', 'Failed to download %file', array(
      '%file' => $filename,
    ), WATCHDOG_ERROR);
    return;
  }

  // accessible file
  if ($fileinfo = _download_count_get_file_by_upload($filepath)) {

    // core upload file
    $fid = $fileinfo->fid;
    $nid = $fileinfo->nid;
    $vid = $fileinfo->vid;
  }
  elseif ($fileinfo = _download_count_get_nodes_by_filefield($filepath)) {

    //cck filefield
    $fid = db_result(db_query("SELECT fid FROM {files} WHERE filepath = '%s'", $filepath));
    $node = array_pop($fileinfo);
    $nid = $node->nid;
    $vid = $node->vid;
  }
  $ip = ip_address();
  $referrer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : NULL;
  $time = time();
  db_query("INSERT INTO {download_count} (fid, nid, uid, vid, ip_address, referrer, timestamp) VALUES (%d, %d, %d, %d, '%s', '%s', %d)", $fid, $nid, $user->uid, $vid, $ip, $referrer, $time);
  watchdog('download_count', '%file was downloaded', array(
    '%file' => $filename,
  ), WATCHDOG_NOTICE);
  if (module_exists('rules')) {
    rules_invoke_event('download_count_file_download', $pathinfo['basename'], $user, $nid, $ip, $referrer, $time);
  }
}

/**
 * Implementation of hook_block().
 *
 */
function download_count_block($op = 'list', $delta = 0, $edit = array()) {
  switch ($op) {
    case 'list':
      $blocks['files']['info'] = t('Top Downloaded Files');
      $blocks['downloaders']['info'] = t('Top Downloaders');
      $blocks['users']['info'] = t('Top Downloaded Users');
      $blocks['nodes']['info'] = t('Top Downloaded Nodes');
      return $blocks;
    case 'configure':
      $form['download_count_' . $delta . '_block_limit'] = array(
        '#type' => 'textfield',
        '#title' => t('Number of items to display'),
        '#size' => 5,
        '#default_value' => variable_get('download_count_' . $delta . '_block_limit', 10),
      );
      $form['download_count_' . $delta . '_show_size'] = array(
        '#type' => 'checkbox',
        '#title' => t('Display aggregated filesize'),
        '#default_value' => variable_get('download_count_' . $delta . '_show_size', 0),
      );
      $form['download_count_' . $delta . '_show_last'] = array(
        '#type' => 'checkbox',
        '#title' => t('Display last download datetime'),
        '#default_value' => variable_get('download_count_' . $delta . '_show_last', 0),
      );
      if ($delta == 'files') {
        $form['download_count_files_file_links'] = array(
          '#type' => 'checkbox',
          '#title' => t('Display files names as links (based on permissions).'),
          '#default_value' => variable_get('download_count_files_file_links', 1),
        );
      }
      return $form;
    case 'save':
      variable_set('download_count_' . $delta . '_block_limit', $edit['download_count_' . $delta . '_block_limit']);
      variable_set('download_count_' . $delta . '_show_size', $edit['download_count_' . $delta . '_show_size']);
      variable_set('download_count_' . $delta . '_show_last', $edit['download_count_' . $delta . '_show_last']);
      $delta == 'files' ? variable_set('download_count_files_file_links', $edit['download_count_files_file_links']) : NULL;
      break;
    case 'view':
      switch ($delta) {
        case 'files':
          $blocks['subject'] = t('Top Downloaded Files');
          $blocks['content'] = _download_count_block_contents('files');
          break;
        case 'downloaders':
          $blocks['subject'] = t('Top Downloaders');
          $blocks['content'] = _download_count_block_contents('downloaders');
          break;
        case 'users':
          $blocks['subject'] = t('Top Downloaded Users');
          $blocks['content'] = _download_count_block_contents('users');
          break;
        case 'nodes':
          $blocks['subject'] = t('Top Downloaded Nodes');
          $blocks['content'] = _download_count_block_contents('nodes');
          break;
      }
      return $blocks;
  }
}

/**
 * internal functions
 */
function _download_count_get_file_by_upload($filepath) {

  // check if Upload is enabled
  if (!function_exists('upload_perm')) {
    return NULL;
  }

  // upload not enabled
  $result = db_query("SELECT u.nid, u.vid, f.filepath, f.fid FROM {upload} u JOIN {files} f ON f.fid = u.fid WHERE f.filepath = '%s'", $filepath);
  return db_fetch_object($result);
}
function _download_count_is_accessible_by_upload($filepath) {
  if ($file = _download_count_get_file_by_upload($filepath)) {
    if (user_access('view uploaded files') && node_access('view', node_load($file->nid))) {
      return TRUE;

      // accessible
    }
    else {
      return FALSE;

      // inaccessible
    }
  }
  return NULL;

  // not known
}
function _download_count_is_accessible_by_filefield($file) {
  $nodes = _download_count_get_nodes_by_filefield($file);
  if ($nodes === FALSE) {
    return FALSE;
  }
  if ($nodes === NULL) {
    return NULL;
  }
  return TRUE;
}
function _download_count_get_nodes_by_filefield($file) {

  // check if FileField is enabled
  if (!function_exists('filefield_view_access')) {
    return NULL;
  }

  // not enabled
  // The following logic is snipped from filefield.module 1.209 2009/10/20 17:46:22
  // which is part of CCK FileField 6.x-3.2 release, see http://drupal.org/project/filefield
  $result = db_query("SELECT * FROM {files} WHERE filepath = '%s'", $file);
  if (!($file = db_fetch_object($result))) {

    // We don't really care about this file.
    return NULL;

    // not known
  }

  // Find out if any file field contains this file, and if so, which field
  // and node it belongs to. Required for later access checking.
  $cck_files = array();
  foreach (content_fields() as $field) {
    if ($field['type'] == 'filefield' || $field['type'] == 'image') {
      $db_info = content_database_info($field);
      $table = $db_info['table'];
      $fid_column = $db_info['columns']['fid']['column'];
      $columns = array(
        'vid',
        'nid',
      );
      foreach ($db_info['columns'] as $property_name => $column_info) {
        $columns[] = $column_info['column'] . ' AS ' . $property_name;
      }
      $result = db_query("SELECT " . implode(', ', $columns) . "\n                          FROM {" . $table . "}\n                          WHERE " . $fid_column . " = %d", $file->fid);
      while ($content = db_fetch_array($result)) {
        $content['field'] = $field;
        $cck_files[$field['field_name']][$content['vid']] = $content;
      }
    }
  }

  // If no file field item is involved with this file, we don't care about it.
  if (empty($cck_files)) {
    return NULL;

    // not known
  }

  // If any node includes this file but the user may not view this field,
  // then deny the download.
  foreach ($cck_files as $field_name => $field_files) {
    if (!filefield_view_access($field_name)) {
      return FALSE;

      // inaccessible
    }
  }

  // So the overall field view permissions are not denied, but if access is
  // denied for ALL nodes containing the file, deny the download as well.
  // Node access checks also include checking for 'access content'.
  $nodes = array();
  $denied = FALSE;
  foreach ($cck_files as $field_name => $field_files) {
    foreach ($field_files as $revision_id => $content) {

      // Checking separately for each revision is probably not the best idea -
      // what if 'view revisions' is disabled? So, let's just check for the
      // current revision of that node.
      if (isset($nodes[$content['nid']])) {
        continue;

        // Don't check the same node twice.
      }
      if ($denied == FALSE && ($node = node_load($content['nid'])) && node_access('view', $node) == FALSE) {

        // You don't have permission to view the node this file is attached to.
        $denied = TRUE;
      }
      $nodes[$content['nid']] = $node;
    }
    if ($denied) {
      return FALSE;

      // inaccessible
    }
  }

  // Access is granted.
  return $nodes;
}
function _download_count_block_contents($block) {
  $result = '';
  $limit = variable_get('download_count_' . $block . '_block_limit', 10);
  $show_size = variable_get('download_count_' . $block . '_show_size', 0);
  $show_last = variable_get('download_count_' . $block . '_show_last', 0);
  $block == 'files' ? $file_links = variable_get('download_count_files_file_links', 1) : NULL;
  switch ($block) {
    case 'files':
      $rows = array();
      $header[] = array(
        'data' => t('Filename'),
        'class' => 'filename',
      );
      $header[] = $show_size ? array(
        'data' => t('Size'),
        'class' => 'size',
      ) : '';
      $header[] = array(
        'data' => t('Count'),
        'class' => 'count',
      );
      $header[] = $show_last ? array(
        'data' => t('Last Downloaded'),
        'class' => 'last',
      ) : '';
      if (user_access('view all download counts')) {
        $result = db_query('SELECT COUNT(dc.dcid) AS count, f.filename, f.filepath, f.fid, SUM(f.filesize) AS size, MAX(dc.timestamp) as last FROM {download_count} dc JOIN {files} f ON dc.fid = f.fid GROUP BY f.filename ORDER BY count DESC LIMIT %d', (int) $limit);
      }
      while ($file = db_fetch_object($result)) {
        $row = array();
        $row[] = $file_links && (user_access('view uploaded files') || _download_count_is_accessible_by_filefield($file->filepath)) ? l(t('@filename', array(
          '@filename' => $file->filename,
        )), function_exists('_private_upload_create_url') ? _private_upload_create_url($file) : file_create_url($file->filepath)) : check_plain($file->filename);
        $row[] = $show_size ? format_size($file->size) : '';
        $row[] = $file->count;
        $row[] = $show_last ? t('@time ago', array(
          '@time' => format_interval(time() - $file->last),
        )) : '';
        $rows[] = $row;
      }
      if (count($rows)) {
        return theme('table', $header, $rows, array(
          'class' => 'no-sticky',
        ));
      }
      return;
    case 'downloaders':
      $rows = array();
      $header[] = array(
        'data' => t('User'),
        'class' => 'user',
      );
      $header[] = $show_size ? array(
        'data' => t('Size'),
        'class' => 'size',
      ) : '';
      $header[] = array(
        'data' => t('Count'),
        'class' => 'count',
      );
      $header[] = $show_last ? array(
        'data' => t('Last Downloaded'),
        'class' => 'last',
      ) : '';
      if (user_access('view all download counts')) {
        $result = db_query('SELECT COUNT(dc.dcid) AS count, SUM(f.filesize) AS size, u.name, u.uid, MAX(dc.timestamp) as last FROM {download_count} dc JOIN {users} u ON dc.uid = u.uid JOIN {files} f on dc.fid = f.fid GROUP BY u.name ORDER BY count DESC LIMIT %d', (int) $limit);
      }
      while ($file = db_fetch_object($result)) {
        $row = array();
        $row[] = l($file->name, 'user/' . $file->uid);
        $row[] = $show_size ? format_size($file->size) : '';
        $row[] = $file->count;
        $row[] = $show_last ? t('@time ago', array(
          '@time' => format_interval(time() - $file->last),
        )) : '';
        $rows[] = $row;
      }
      if (count($rows)) {
        return theme('table', $header, $rows, array(
          'class' => 'no-sticky',
        ));
      }
      return;
    case 'users':
      $rows = array();
      $header[] = array(
        'data' => t('User'),
        'class' => 'user',
      );
      $header[] = $show_size ? array(
        'data' => t('Size'),
        'class' => 'size',
      ) : '';
      $header[] = array(
        'data' => t('Count'),
        'class' => 'count',
      );
      $header[] = $show_last ? array(
        'data' => t('Last Downloaded'),
        'class' => 'last',
      ) : '';
      if (user_access('view all download counts')) {
        $result = db_query('SELECT COUNT(dc.dcid) AS count, SUM(f.filesize) AS size, u.name, u.uid, MAX(dc.timestamp) as last FROM {download_count} dc JOIN {files} f on dc.fid = f.fid JOIN {users} u ON f.uid = u.uid GROUP BY u.name ORDER BY count DESC LIMIT %d', (int) $limit);
      }
      while ($file = db_fetch_object($result)) {
        $row = array();
        $row[] = l($file->name, 'user/' . $file->uid);
        $row[] = $show_size ? format_size($file->size) : '';
        $row[] = $file->count;
        $row[] = $show_last ? t('@time ago', array(
          '@time' => format_interval(time() - $file->last),
        )) : '';
        $rows[] = $row;
      }
      if (count($rows)) {
        return theme('table', $header, $rows, array(
          'class' => 'no-sticky',
        ));
      }
      return;
    case 'nodes':
      $rows = array();
      $header[] = array(
        'data' => t('Page'),
        'class' => 'node',
      );
      $header[] = $show_size ? array(
        'data' => t('Size'),
        'class' => 'size',
      ) : '';
      $header[] = array(
        'data' => t('Count'),
        'class' => 'count',
      );
      $header[] = $show_last ? array(
        'data' => t('Last Downloaded'),
        'class' => 'last',
      ) : '';
      if (user_access('view all download counts')) {
        $result = db_query('SELECT COUNT(dc.dcid) AS count, SUM(f.filesize) AS size, dc.nid, MAX(dc.timestamp) as last FROM {download_count} dc JOIN {files} f on dc.fid = f.fid GROUP BY dc.nid ORDER BY count DESC LIMIT %d', (int) $limit);
      }
      while ($file = db_fetch_object($result)) {
        $row = array();
        $node = node_load($file->nid);
        $row[] = node_access('view', $node) ? l($node->title, 'node/' . $node->nid) : check_plain($node->title);
        $row[] = $show_size ? format_size($file->size) : '';
        $row[] = $file->count;
        $row[] = $show_last ? t('@time ago', array(
          '@time' => format_interval(time() - $file->last),
        )) : '';
        $rows[] = $row;
      }
      if (count($rows)) {
        return theme('table', $header, $rows, array(
          'class' => 'no-sticky',
        ));
      }
      return;
    default:
      return;
  }
}

/**
 * Implementation of hook_rules_event_info().
 * @ingroup rules
 */
function download_count_rules_event_info() {
  return array(
    'download_count_file_download' => array(
      'label' => t('A file has been downloaded'),
      'module' => 'Download Count',
      'arguments' => array(
        'filename' => array(
          'type' => 'file',
          'label' => t('Filename of the downloaded file.'),
        ),
        'user' => array(
          'type' => 'file',
          'label' => t('User object of the user downloading the file.'),
        ),
        'nid' => array(
          'type' => 'file',
          'label' => t('Node from which the file was downloaded.'),
        ),
        'ip' => array(
          'type' => 'file',
          'label' => t('IP address to which the file was downloaded.'),
        ),
        'referrer' => array(
          'type' => 'file',
          'label' => t('HTTP Referrer of the download link.'),
        ),
        'time' => array(
          'type' => 'file',
          'label' => t('Timestamp of the download.'),
        ),
      ),
    ),
  );
}