You are here

filehash.module in File Hash 7

Same filename and directory in other branches
  1. 8 filehash.module

Generate hashes for each uploaded file.

File

filehash.module
View source
<?php

/**
 * @file
 * Generate hashes for each uploaded file.
 */

/**
 * Implements hook_menu().
 */
function filehash_menu() {
  $items['admin/config/media/filehash'] = array(
    'title' => 'File hash',
    'description' => 'Configure File Hash settings.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'filehash_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'filehash.admin.inc',
  );
  $items['admin/config/media/filehash/settings'] = array(
    'title' => 'Settings',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/config/media/filehash/bulk'] = array(
    'title' => 'Bulk generate',
    'description' => 'Generates hashes for each uploaded file.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'filehash_bulk',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'filehash.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );
  return $items;
}

/**
 * Returns array of enabled hash algorithms.
 */
function filehash_algos() {
  return array_diff(variable_get('filehash_algos', array()), array(
    0,
  ));
}

/**
 * Implements hook_file_delete().
 */
function filehash_file_delete($file) {
  db_delete('filehash')
    ->condition('fid', $file->fid)
    ->execute();
}

/**
 * Implements hook_file_insert().
 */
function filehash_file_insert($file) {
  filehash_save($file);
}

/**
 * Implements hook_file_load().
 */
function filehash_file_load($files) {
  $algos = filehash_algos();
  if ($algos) {
    $result = db_select('filehash')
      ->fields('filehash')
      ->condition('fid', array_keys($files), 'IN')
      ->execute();
    foreach ($result as $record) {
      foreach ($algos as $algo) {
        $files[$record->fid]->filehash[$algo] = $record->{$algo};
      }
    }

    // Generate hash if it does not already exist for the file.
    foreach ($files as $fid => $file) {
      foreach ($algos as $algo) {
        if (empty($file->filehash[$algo])) {
          filehash_save($files[$fid]);
          break;
        }
      }
    }
  }
}

/**
 * Implements hook_file_update().
 */
function filehash_file_update($file) {
  filehash_save($file);
}

/**
 * Implements hook_file_validate().
 */
function filehash_file_validate($file) {
  $errors = array();

  // Zero size may indicate unreadable remote media source; bypass dedupe.
  if ($file->filesize && variable_get('filehash_dedupe', 0)) {
    foreach (filehash_algos() as $algo) {
      if ($fid = db_query("SELECT fid FROM {filehash} WHERE {$algo} = :hash LIMIT 1", array(
        ':hash' => hash_file($algo, $file->uri),
      ))
        ->fetchField()) {
        $duplicate = file_load($fid);
        if (file_download_access($duplicate->uri)) {
          $errors[] = t('This file has already been uploaded as %filename.', array(
            '%filename' => $duplicate->filename,
          ));
        }
        else {
          $errors[] = t('Sorry, duplicate files are not permitted.');
        }
        break;
      }
    }
  }
  return $errors;
}

/**
 * Returns array of human-readable hash algorithm names.
 */
function filehash_names() {
  return array(
    'md5' => 'MD5',
    'sha1' => 'SHA-1',
    'sha256' => 'SHA-256',
  );
}

/**
 * Implements hook_node_view().
 */
function filehash_node_view($node, $view_mode) {
  static $fields;

  // Add <media:hash> elements for at most one file per RSS item.
  if ($view_mode != 'rss') {
    return;
  }

  // The <media:hash> element only supports MD5 and SHA-1.
  $algos = filehash_algos();
  if (!isset($algos['md5']) && !isset($algos['sha1'])) {
    return;
  }
  if (!isset($fields[$node->type])) {
    $fields[$node->type] = field_read_fields(array(
      'bundle' => $node->type,
      'entity_type' => 'node',
      // The following field types are currently supported.
      'type' => array(
        'file',
        'image',
        'media',
      ),
    ));
  }
  foreach ($fields[$node->type] as $field) {
    if ($items = field_get_items('node', $node, $field['field_name'])) {
      foreach ($items as $file) {

        // Media fields have a file object rather than a file array.
        if ($field['type'] == 'media') {
          filehash_rss_elements((array) $file['file'], $node);
          return;
        }
        elseif ($field['type'] == 'image' || $file['display']) {
          filehash_rss_elements($file, $node);
          return;
        }
      }
    }
  }
}

/**
 * Adds <media:hash> RSS elements to $node object.
 */
function filehash_rss_elements(array $file, $node) {
  $names = array(
    'md5' => 'md5',
    'sha1' => 'sha-1',
  );
  foreach ($names as $algo => $name) {
    if (!empty($file['filehash'][$algo])) {
      $node->rss_elements[] = array(
        'key' => 'media:hash',
        'attributes' => array(
          'algo' => $name,
        ),
        'value' => $file['filehash'][$algo],
      );
    }
  }
  $node->rss_namespaces['xmlns:media'] = 'http://search.yahoo.com/mrss/';
}

/**
 * Calculates and saves the file hashes.
 */
function filehash_save($file) {
  $file->filehash = array_fill_keys(array(
    'md5',
    'sha1',
    'sha256',
  ), NULL);
  foreach (filehash_algos() as $algo) {
    $file->filehash[$algo] = hash_file($algo, $file->uri);
  }
  db_merge('filehash')
    ->key(array(
    'fid' => $file->fid,
  ))
    ->fields($file->filehash)
    ->execute();
}

/**
 * Implements hook_views_api().
 */
function filehash_views_api() {
  return array(
    'api' => 3,
  );
}

/**
 * Implements hook_field_formatter_info().
 */
function filehash_field_formatter_info() {
  $algos = filehash_algos();
  return array(
    'filehash_table' => array(
      'label' => t('Table of files with file hashes'),
      'field types' => array(
        'file',
        'image',
        'media',
      ),
      'settings' => array(
        'algo' => array_pop($algos),
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function filehash_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $names = filehash_names();
  $options = array();
  foreach (filehash_algos() as $algo) {
    $options[$algo] = $names[$algo];
  }
  $element['algo'] = array(
    '#title' => t('Hash algorithm'),
    '#type' => 'select',
    '#default_value' => $instance['display'][$view_mode]['settings']['algo'],
    '#options' => $options,
  );
  return $element;
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function filehash_field_formatter_settings_summary($field, $instance, $view_mode) {
  $algos = filehash_names();
  if (isset($algos[$instance['display'][$view_mode]['settings']['algo']])) {
    return t('@algo hash', array(
      '@algo' => $algos[$instance['display'][$view_mode]['settings']['algo']],
    ));
  }
}

/**
 * Implements hook_field_formatter_view().
 */
function filehash_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
  if (!empty($items)) {
    $element[0] = array(
      '#theme' => 'filehash_table',
      '#items' => $items,
      '#settings' => $display['settings'],
    );
  }
  return $element;
}

/**
 * Returns HTML for a file attachments table with hashes.
 *
 * @param array $variables
 *   An associative array containing:
 *   - items: An array of file attachments.
 *   - settings: The field instance display settings.
 *
 * @ingroup themeable
 */
function theme_filehash_table(array $variables) {
  $names = filehash_names();
  $header = array(
    t('Attachment'),
    t('Size'),
    t('@algo hash', array(
      '@algo' => $names[$variables['settings']['algo']],
    )),
  );
  $rows = array();
  foreach ($variables['items'] as $item) {

    // Media field items contain a file object.
    if (!isset($item['uri']) && isset($item['file'])) {
      $file = $item['file'];
    }
    else {
      $file = (object) $item;
    }
    $rows[] = array(
      theme('file_link', array(
        'file' => $file,
      )),
      format_size($file->filesize),
      substr(chunk_split($file->filehash[$variables['settings']['algo']], 1, '<wbr />'), 0, -7),
    );
  }
  return empty($rows) ? '' : theme('table', array(
    'header' => $header,
    'rows' => $rows,
  ));
}

/**
 * Implements hook_theme().
 */
function filehash_theme() {
  return array(
    'filehash_table' => array(
      'variables' => array(
        'items' => array(),
        'settings' => array(),
      ),
    ),
  );
}

Functions

Namesort descending Description
filehash_algos Returns array of enabled hash algorithms.
filehash_field_formatter_info Implements hook_field_formatter_info().
filehash_field_formatter_settings_form Implements hook_field_formatter_settings_form().
filehash_field_formatter_settings_summary Implements hook_field_formatter_settings_summary().
filehash_field_formatter_view Implements hook_field_formatter_view().
filehash_file_delete Implements hook_file_delete().
filehash_file_insert Implements hook_file_insert().
filehash_file_load Implements hook_file_load().
filehash_file_update Implements hook_file_update().
filehash_file_validate Implements hook_file_validate().
filehash_menu Implements hook_menu().
filehash_names Returns array of human-readable hash algorithm names.
filehash_node_view Implements hook_node_view().
filehash_rss_elements Adds <media:hash> RSS elements to $node object.
filehash_save Calculates and saves the file hashes.
filehash_theme Implements hook_theme().
filehash_views_api Implements hook_views_api().
theme_filehash_table Returns HTML for a file attachments table with hashes.