You are here

filehash.module in File Hash 8

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

Generate hashes for each uploaded file.

File

filehash.module
View source
<?php

/**
 * @file
 * Generate hashes for each uploaded file.
 */
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\file\Entity\File;
use Drupal\file\FileInterface;
use Drupal\Core\Routing\RouteMatchInterface;

/**
 * Implements hook_help().
 */
function filehash_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.filehash':
      return [
        '#type' => 'html_tag',
        '#tag' => 'p',
        '#value' => t('File Hash module generates and stores MD5, SHA-1 and/or SHA-256 hashes for each file uploaded to the site. Hashes allow files to be uniquely identified, duplicate files to be detected, and copies to be verified against the original source.'),
      ];
  }
}

/**
 * Returns array of enabled hash algorithms.
 */
function filehash_algos() {
  return array_diff(\Drupal::config('filehash.settings')
    ->get('algos'), [
    0,
  ]);
}

/**
 * Implements hook_ENTITY_TYPE_create().
 */
function filehash_file_create(EntityInterface $file) {
  filehash_hash($file);
}

/**
 * Implements hook_ENTITY_TYPE_delete().
 */
function filehash_file_delete(EntityInterface $file) {
  \Drupal::database()
    ->delete('filehash')
    ->condition('fid', $file
    ->id())
    ->execute();
}

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

/**
 * Implements hook_ENTITY_TYPE_load().
 */
function filehash_file_load($files) {
  $algos = filehash_algos();
  if (!$algos) {
    return;
  }
  $result = \Drupal::database()
    ->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_hash($files[$fid]);
        filehash_save($files[$fid]);
        break;
      }
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_presave().
 */
function filehash_file_presave(EntityInterface $file) {
  filehash_hash($file);
}

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

/**
 * Implements hook_file_validate().
 */
function filehash_file_validate(FileInterface $file) {
  return \Drupal::config('filehash.settings')
    ->get('dedupe') ? filehash_validate_dedupe($file) : [];
}

/**
 * Checks that file is not a duplicate.
 */
function filehash_validate_dedupe(FileInterface $file) {
  $errors = [];

  // We only run the dedupe check on initial file creation.
  if (!$file
    ->id()) {
    foreach (filehash_algos() as $algo) {
      $query = \Drupal::database()
        ->select('filehash');
      $query
        ->addField('filehash', 'fid');
      $query
        ->condition($algo, $file->filehash[$algo]);
      $query
        ->range(0, 1);
      if ($fid = $query
        ->execute()
        ->fetchField()) {
        $error = t('Sorry, duplicate files are not permitted.');
        if (\Drupal::currentUser()
          ->hasPermission('access files overview')) {
          try {
            $url = Url::fromRoute('view.files.page_2', [
              'arg_0' => $fid,
            ], [
              'attributes' => [
                'target' => '_blank',
              ],
            ]);
            $error = t('This file has already been uploaded as %filename.', [
              '%filename' => Link::fromTextAndUrl(File::load($fid)
                ->label(), $url)
                ->toString(),
            ]);
          } catch (Exception $e) {

            // Maybe the view was disabled?
          }
        }
        $errors[] = $error;
        break;
      }
    }
  }
  return $errors;
}

/**
 * Calculates the file hashes.
 */
function filehash_hash($file) {
  $file->filehash = array_fill_keys([
    'md5',
    'sha1',
    'sha256',
  ], NULL);

  // Unreadable files will have NULL hash values.
  if (!is_readable($file
    ->getFileUri())) {
    return;
  }
  foreach (filehash_algos() as $algo) {
    $file->filehash[$algo] = hash_file($algo, $file
      ->getFileUri());
  }
}

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

/**
 * Implements hook_ENTITY_TYPE_build_defaults_alter().
 */
function filehash_node_build_defaults_alter(array &$build, EntityInterface $node, $view_mode = 'full', $langcode = NULL) {
  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;
  }

  // The following field types are currently supported.
  $fields = \Drupal::entityTypeManager()
    ->getStorage('field_config')
    ->loadByProperties([
    'entity_type' => 'node',
    'bundle' => $node
      ->bundle(),
    'field_type' => 'file',
  ]);
  $fields += \Drupal::entityTypeManager()
    ->getStorage('field_config')
    ->loadByProperties([
    'entity_type' => 'node',
    'bundle' => $node
      ->bundle(),
    'field_type' => 'image',
  ]);
  foreach ($fields as $field) {
    if (method_exists($field, 'getName')) {
      foreach ($node->{$field
        ->getName()} as $item) {
        if ($item
          ->isDisplayed()) {

          // Add <media:hash> elements for at most one file per RSS item.
          $file = File::load($item->target_id);
          filehash_rss_elements($file, $node);
          return;
        }
      }
    }
  }
}

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

/**
 * Saves the file hashes.
 */
function filehash_save(FileInterface $file) {
  \Drupal::database()
    ->merge('filehash')
    ->key([
    'fid' => $file
      ->id(),
  ])
    ->fields($file->filehash)
    ->execute();
}

Functions

Namesort descending Description
filehash_algos Returns array of enabled hash algorithms.
filehash_file_create Implements hook_ENTITY_TYPE_create().
filehash_file_delete Implements hook_ENTITY_TYPE_delete().
filehash_file_insert Implements hook_ENTITY_TYPE_insert().
filehash_file_load Implements hook_ENTITY_TYPE_load().
filehash_file_presave Implements hook_ENTITY_TYPE_presave().
filehash_file_update Implements hook_ENTITY_TYPE_update().
filehash_file_validate Implements hook_file_validate().
filehash_hash Calculates the file hashes.
filehash_help Implements hook_help().
filehash_names Returns array of human-readable hash algorithm names.
filehash_node_build_defaults_alter Implements hook_ENTITY_TYPE_build_defaults_alter().
filehash_rss_elements Adds <media:hash> RSS elements to $node object.
filehash_save Saves the file hashes.
filehash_validate_dedupe Checks that file is not a duplicate.