You are here

auditfiles.usednotreferenced.inc in Audit Files 7.4

Same filename and directory in other branches
  1. 7.3 auditfiles.usednotreferenced.inc

Generates report showing files in file_usage, but not referenced by content.

File

auditfiles.usednotreferenced.inc
View source
<?php

/**
 * @file
 * Generates report showing files in file_usage, but not referenced by content.
 */

/**
 * The following are functions for displaying the list of files on the page.
 */

/**
 * Generates the report.
 *
 * @param array $form
 *   The form definition.
 * @param array $form_state
 *   The current state of the form.
 *
 * @return array
 *   The form definition.
 */
function auditfiles_used_not_referenced_form(array $form, array &$form_state) {

  // Check to see if the confirmation form needs to be displayed instead of the
  // normal form.
  if (isset($form_state['storage']['confirm'])) {
    return _auditfiles_used_not_referenced_confirm_operation($form, $form_state);
  }
  $output = '<p>' . t("Any file references listed below are in the file_usage\n    database table, but no content is recorded as referencing them. This might\n    mean the content or field has been deleted without deleting the file, or\n    that the files were uploaded by some means other than through a mechanizm\n    that utilized Drupal's file management, and the relationships between files\n    and content have not been made.\n    Click on the basename to get a list\n    of all nodes referencing that base filename (the <strong>Missing\n    references</strong> report only identifies <strong>&lt;img&gt;</strong>\n    and <strong>window.open</strong> references, this may help you identify\n    if the file is referenced by another means).");
  $output .= t('The files in this list are using the %scheme scheme and are
    relative to the files directory path, located at %path.', array(
    '%scheme' => file_default_scheme(),
    '%path' => variable_get('file_' . file_default_scheme() . '_path', conf_path() . '/files'),
  )) . '</p>';
  $form['introduction'] = array(
    '#markup' => $output,
  );

  // Get the records to display.
  // Check to see if the data has been stored.
  if (!empty($form_state['storage']['saved_rows'])) {

    // The data is currently saved, so use that.
    $rows = unserialize($form_state['storage']['saved_rows']);
  }
  else {

    // Check to see if the batch operation was just run. If so, use that data.
    $rows = variable_get('auditfiles_used_not_referenced_files_to_display', array());
    if (empty($rows)) {

      // The data is not saved and the batch operation has not been run, so get
      // the data using the default options.
      $file_ids = _auditfiles_used_not_referenced_get_file_list();
      if (!empty($file_ids)) {
        foreach ($file_ids as $file_id) {
          $rows[$file_id] = _auditfiles_used_not_referenced_get_file_data($file_id);
        }
      }
    }
  }

  // Save the data for later retrieval.
  $form['saved_rows'] = array(
    '#type' => 'hidden',
    '#value' => serialize($rows),
  );

  // Set up the pager.
  if (!empty($rows)) {
    $items_per_page = variable_get('auditfiles_report_options_items_per_page', 50);
    if (!empty($items_per_page)) {
      $current_page = pager_default_initialize(count($rows), $items_per_page);

      // Break the total data set into page sized chunks.
      $pages = array_chunk($rows, $items_per_page, TRUE);
    }
  }

  // Get any specified record selection limiters.
  $record_limiters = _auditfiles_used_not_referenced_get_record_limiters();
  $record_selection = $record_limiters['record_selection'];
  $maximum_records = $record_limiters['maximum_records'];

  // Define the form.
  // Setup the record count and related messages.
  if (!empty($rows)) {
    if ($record_selection == 'batch_sets') {
      $file_count_message = 'Found @count files in this batch set in the file_usage table that are not referenced in content.';
    }
    elseif ($maximum_records > 0) {
      $file_count_message = 'Found at least @count files in the file_usage table that are not referenced in content.';
    }
    else {
      $file_count_message = 'Found @count files in the file_usage table that are not referenced in content.';
    }
    $form_count = format_plural(count($rows), 'Found 1 file in the file_usage table that is not referenced in content.', $file_count_message);
  }
  else {
    $form_count = 'Found no files in the file_usage table that are not referenced in content.';
  }

  // Add the button to batch process the list of results.
  if ($record_selection == 'limited') {
    $batch_size = variable_get('auditfiles_report_options_batch_size', 0);
    if ($batch_size > 0) {
      $form['batch_process'] = array(
        '#type' => 'submit',
        '#value' => t('Load first batch set'),
      );
    }
    else {
      $form['batch_process'] = array(
        '#type' => 'submit',
        '#value' => t('Load all records'),
      );
    }
  }
  elseif ($record_selection == 'batch_sets') {

    // Add the button to load the previous batch set.
    $form['batch_process_prev'] = array(
      '#type' => 'submit',
      '#value' => t('Load previous batch set'),
    );

    // Add the button to load the next batch set.
    $form['batch_process_next'] = array(
      '#type' => 'submit',
      '#value' => t('Load next batch set'),
    );
  }

  // Add the button to reset the record selection.
  if ($record_selection != 'normal') {
    $form['reset_records'] = array(
      '#type' => 'submit',
      '#value' => t('Reset record selection'),
    );
  }
  $form['files'] = array(
    '#type' => 'tableselect',
    '#header' => _auditfiles_used_not_referenced_get_header(),
    '#empty' => t('No items found.'),
    '#prefix' => '<div><em>' . $form_count . '</em></div>',
  );
  if (!empty($rows) && !empty($pages)) {
    $form['files']['#options'] = $pages[$current_page];
  }
  elseif (!empty($rows)) {
    $form['files']['#options'] = $rows;
  }
  else {
    $form['files']['#options'] = array();
  }
  if (!empty($rows)) {
    $form['actions'] = array(
      '#type' => 'actions',
    );
    $form['actions']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Delete selected items from the file_usage table'),
    );
    $form['pager'] = array(
      '#markup' => theme('pager'),
    );
  }
  return $form;
}

/**
 * Submit handler for the auditfiles_used_not_referenced_form form.
 */
function auditfiles_used_not_referenced_form_submit(array $form, array &$form_state) {

  // Check if an operation was performed.
  if (!empty($form_state['values']['op'])) {

    // Check which operation was performed and start the batch process.
    if ($form_state['values']['op'] == 'Load all records' || $form_state['values']['op'] == 'Load first batch set') {

      // Clear the variable, so subsequent pages will load the correct data.
      variable_del('auditfiles_files_to_display');
      $batch_size = variable_get('auditfiles_report_options_batch_size', 0);
      if ($batch_size > 0) {

        // Set appropriate variables for this operation.
        variable_set('auditfiles_used_not_referenced_record_selection', 'batch_sets');
      }
      else {

        // Set appropriate variables for this operation.
        variable_set('auditfiles_used_not_referenced_record_selection', 'batched');
      }

      // Prepare and set the batch.
      batch_set(_auditfiles_used_not_referenced_batch_display_create_batch());
    }
    elseif ($form_state['values']['op'] == 'Load previous batch set') {

      // Clear the variable, so subsequent pages will load the correct data.
      variable_del('auditfiles_files_to_display');

      // Set appropriate variables for this operation.
      $batch_size = variable_get('auditfiles_report_options_batch_size', 0);
      $batch_offset = variable_get('auditfiles_used_not_referenced_batch_offset', 0);
      variable_set('auditfiles_used_not_referenced_batch_offset', $batch_offset - $batch_size);
      variable_set('auditfiles_used_not_referenced_record_selection', 'batch_sets');

      // Prepare and set the batch.
      batch_set(_auditfiles_used_not_referenced_batch_display_create_batch());
    }
    elseif ($form_state['values']['op'] == 'Load next batch set') {

      // Clear the variable, so subsequent pages will load the correct data.
      variable_del('auditfiles_files_to_display');

      // Set appropriate variables for this operation.
      $batch_size = variable_get('auditfiles_report_options_batch_size', 0);
      $batch_offset = variable_get('auditfiles_used_not_referenced_batch_offset', 0);
      variable_set('auditfiles_used_not_referenced_batch_offset', $batch_offset + $batch_size);
      variable_set('auditfiles_used_not_referenced_record_selection', 'batch_sets');

      // Prepare and set the batch.
      batch_set(_auditfiles_used_not_referenced_batch_display_create_batch());
    }
    elseif ($form_state['values']['op'] == 'Reset record selection') {

      // Clear the variable, so subsequent pages will load the correct data.
      variable_del('auditfiles_files_to_display');

      // Set appropriate variables for this operation.
      unset($form_state['values']['saved_rows']);
      variable_set('auditfiles_used_not_referenced_record_selection', 'normal');
      variable_del('auditfiles_used_not_referenced_batch_offset');
    }
    elseif ($form_state['values']['op'] == 'Delete selected items from the file_usage table' && !empty($form_state['values']['files'])) {
      foreach ($form_state['values']['files'] as $file_id) {
        if (!empty($file_id)) {

          // At least one file was selected, and the operation has not been
          // confirmed, so modify the data to display the confirmation form.
          $form_state['storage']['files'] = $form_state['values']['files'];
          $form_state['storage']['op'] = $form_state['values']['op'];
          $form_state['storage']['confirm'] = TRUE;
          $form_state['rebuild'] = TRUE;
          return TRUE;
        }
      }
      drupal_set_message(t('No items were selected to operate on.'));
    }
    elseif ($form_state['values']['op'] == 'Yes') {
      if ($form_state['values']['operation'] == 'delete') {

        // Prepare and set the batch.
        batch_set(_auditfiles_used_not_referenced_batch_delete_create_batch($form_state['values']['changelist']));
      }
    }
  }
  if (!empty($form_state['values']['saved_rows'])) {
    $form_state['storage']['saved_rows'] = $form_state['values']['saved_rows'];
  }
}

/**
 * The following are functions that are common for all batches in this file.
 */

/**
 * Adds vaules to a batch definition that are common to all batches in the file.
 *
 * @return array
 *   The beginning of the batch definition.
 */
function _auditfiles_used_not_referenced_batch_set_common_values() {
  return array(
    'error_message' => t('One or more errors were encountered processing the files.'),
    'file' => drupal_get_path('module', 'auditfiles') . '/auditfiles.usednotreferenced.inc',
    'finished' => '_auditfiles_used_not_referenced_batch_finish_batch',
    'progress_message' => t('Completed @current of @total operations.'),
  );
}

/**
 * The function that is called when the batch is complete.
 */
function _auditfiles_used_not_referenced_batch_finish_batch($success, $results, $operations) {
  if ($success) {
    if (!empty($results['files_to_display'])) {

      // Save the gathered data for display.
      variable_set('auditfiles_files_to_display', $results['files_to_display']);
    }
  }
  else {

    // An error occurred.
    // $operations contains the operations that remained unprocessed.
    $error_operation = reset($operations);
    drupal_set_message(t('An error occurred while processing @operation with arguments : @args', array(
      '@operation' => $error_operation[0],
      '@args' => print_r($error_operation[0], TRUE),
    )));
  }
}

/**
 * The following are functions for preparing the batch for displaying the files.
 */

/**
 * Prepares the definition for the page display batch.
 *
 * @return array
 *   The batch definition.
 */
function _auditfiles_used_not_referenced_batch_display_create_batch() {
  $batch = _auditfiles_used_not_referenced_batch_set_common_values();
  $batch['title'] = t('Loading file audit data');
  $batch['operations'] = _auditfiles_used_not_referenced_batch_display_get_operations();
  return $batch;
}

/**
 * Configures the operations for the batch process.
 *
 * @return array
 *   The operations to execute.
 */
function _auditfiles_used_not_referenced_batch_display_get_operations() {
  $operations = array();
  $file_ids = _auditfiles_used_not_referenced_get_file_list();
  foreach ($file_ids as $file_id) {
    $operations[] = array(
      '_auditfiles_used_not_referenced_batch_display_process_operation',
      array(
        $file_id,
      ),
    );
  }
  return $operations;
}

/**
 * The batch process for displaying the files.
 *
 * @param int $file_id
 *   The ID for the file being processed.
 * @param array $context
 *   Used by the Batch API to keep track of and pass data from one operation to
 *   the next.
 */
function _auditfiles_used_not_referenced_batch_display_process_operation($file_id, array &$context) {

  // Process the current file.
  $file = _auditfiles_used_not_referenced_get_file_data($file_id);

  // The contents of 'results' are available as $results in the 'finished'
  // function.
  $context['results']['files_to_display'][$file_id] = $file;

  // Set a progress message.
  $context['message'] = t('Processed file ID %file_id.', array(
    '%file_id' => $file_id,
  ));
}

/**
 * The following are functions for the batch delete operation.
 */

/**
 * Presents a confimation form to verify the user wants to complete the action.
 *
 * @param array $form
 *   The form definition.
 * @param array $form_state
 *   The current state of the form.
 *
 * @return array
 *   A form array for a confirmation form.
 */
function _auditfiles_used_not_referenced_confirm_operation(array $form, array &$form_state) {
  $values = $form_state['values'];
  $form['changelist'] = array(
    '#prefix' => '<ul>',
    '#suffix' => '</ul>',
    '#tree' => TRUE,
  );

  // Prepare the list of items to present to the user.
  if (!empty($values['files'])) {
    foreach ($values['files'] as $file_id) {
      if (!empty($file_id)) {
        $file = file_load($file_id);
        if (!empty($file)) {
          $form['changelist'][$file_id] = array(
            '#type' => 'hidden',
            '#value' => $file_id,
            '#prefix' => '<li><strong>' . $file->filename . '</strong> ' . t('will be deleted from the file_usage table.'),
            '#suffix' => "</li>\n",
          );
        }
      }
      else {

        // Unsetting the unprocessed files prevents confirm_submit from dealing
        // with them.
        unset($form_state['values']['files'][$file_id]);
      }
    }
  }
  $form['operation'] = array(
    '#type' => 'hidden',
    '#value' => 'delete',
  );

  // Tell the submit handler to process the confirmation.
  $form['process'] = array(
    '#type' => 'hidden',
    '#value' => 'TRUE',
  );

  // Go back to the main form, when done with this one.
  $form['destination'] = array(
    '#type' => 'hidden',
    '#value' => 'admin/reports/auditfiles/usednotreferenced',
  );
  return confirm_form($form, t('Delete these items from the file_usage table?'), 'admin/reports/auditfiles/usednotreferenced', '<strong>' . t('This action cannot be undone.') . '</strong>', t('Yes'), t('No'));
}

/**
 * Creates the batch for deleting files from the file_usage table.
 *
 * @param array $fileids
 *   The list of file IDs to be processed.
 *
 * @return array
 *   The definition of the batch.
 */
function _auditfiles_used_not_referenced_batch_delete_create_batch(array $fileids) {
  $batch = _auditfiles_used_not_referenced_batch_set_common_values();
  $batch['title'] = t('Deleting files from the file_usage table');
  $operations = array();

  // Remove all the empty values from the array.
  $file_ids = array();
  foreach ($fileids as $file_id) {
    if ($file_id != 0) {
      $file_ids[] = $file_id;
    }
  }

  // Fill in the $operations variable.
  foreach ($file_ids as $file_id) {
    $operations[] = array(
      '_auditfiles_used_not_referenced_batch_delete_process_batch',
      array(
        $file_id,
      ),
    );
  }
  $batch['operations'] = $operations;
  return $batch;
}

/**
 * The batch process for deleting the file.
 *
 * @param int $file_id
 *   The ID of a file to delete.
 * @param array $context
 *   Used by the Batch API to keep track of and pass data from one operation to
 *   the next.
 */
function _auditfiles_used_not_referenced_batch_delete_process_batch($file_id, array &$context) {

  // Process the current file.
  _auditfiles_used_not_referenced_batch_delete_process_file($file_id);

  // The contents of 'results' are available as $results in the 'finished'
  // function.
  $context['results'][] = $file_id;

  // Set a progress message.
  $context['message'] = t('Processed file ID %file_id.', array(
    '%file_id' => $file_id,
  ));
}

/**
 * Deletes the specified file from the file_usage table.
 *
 * @param int $file_id
 *   The ID of the file to delete from the database.
 */
function _auditfiles_used_not_referenced_batch_delete_process_file($file_id) {
  $num_rows = db_delete('file_usage')
    ->condition('fid', $file_id)
    ->execute();
  if (empty($num_rows)) {
    drupal_set_message(t('There was a problem deleting the record with file ID %fid from the file_usage table. Check the logs for more information.', array(
      '%fid' => $file_id,
    )), 'warning');
  }
  else {

    // Remove the deleted files from the list of files to display.
    $rows = variable_get('auditfiles_used_not_referenced_files_to_display', array());
    unset($rows[$file_id]);
    variable_set('auditfiles_used_not_referenced_files_to_display', $rows);
  }
}

/**
 * The following are functions for retrieving and processing the file data.
 */

/**
 * Retrieves the file IDs to operate on.
 *
 * @return array
 *   The file IDs.
 */
function _auditfiles_used_not_referenced_get_file_list() {

  // Get a list of files in the file_usage database table.
  $query = "SELECT DISTINCT(fid) FROM {file_usage} fu";

  // Add any specified sorting.
  $query_parameters = drupal_get_query_parameters();
  if (!empty($query_parameters['sort']) && !empty($query_parameters['order'])) {
    $headers = _auditfiles_used_not_referenced_get_header();
    foreach ($headers as $header) {
      if ($header['data'] == $query_parameters['order']) {
        $query .= " ORDER BY " . $header['field'] . ' ' . $query_parameters['sort'];
      }
    }
  }

  // If record limition has been configured, only use those records within that
  // specification.
  $record_limiters = _auditfiles_used_not_referenced_get_record_limiters();
  $maximum_records = $record_limiters['maximum_records'];
  if ($maximum_records > 0) {
    $query .= " LIMIT " . $maximum_records;

    // Set the offest.
    $query .= " OFFSET " . variable_get('auditfiles_used_not_referenced_batch_offset', 0);
  }
  else {
    variable_set('auditfiles_used_not_referenced_batch_offset', 0);
  }
  $files_in_file_usage = db_query($query)
    ->fetchCol();
  $files_in_fields = array();

  // Get a list of all fields on the site.
  $fields = field_info_fields();
  foreach ($fields as $field) {

    // @todo
    // Consider adding a setting to allow the administrator the ability for
    // spcifying the field types.
    if (!empty($field['type']) && ($field['type'] == 'file' || $field['type'] == 'image')) {

      // Get the database table name for the field.
      $table = key($field['storage']['details']['sql']['FIELD_LOAD_CURRENT']);

      // Get the column name in the database table for the field.
      $column = $field['storage']['details']['sql']['FIELD_LOAD_CURRENT'][$table]['fid'];
      $query = "SELECT t.{$column} FROM {$table} AS t INNER JOIN {file_usage} AS f ON f.fid = t.{$column}";
      $result = db_query($query);
      foreach ($result as $fid) {
        if (in_array($fid->{$column}, $files_in_file_usage)) {
          $files_in_fields[] = $fid->{$column};
        }
      }
    }
  }
  return array_diff($files_in_file_usage, $files_in_fields);
}

/**
 * Retrieves information about an individual file from the database.
 *
 * @param int $file_id
 *   The ID of the file to prepare for display.
 *
 * @return array
 *   The row for the table on the report, with the file's information formatted
 *   for display.
 */
function _auditfiles_used_not_referenced_get_file_data($file_id) {

  // Get the file's information from the file_managed table.
  $file_managed = db_query("SELECT * FROM {file_managed} fm WHERE fid = {$file_id}")
    ->fetchObject();
  if (empty($file_managed)) {

    // The file is not listed in the file_managed table. Display an error
    // message, instead of the file information.
    $row = array(
      'fid' => t('This file is not listed in the file_managed table. See the ":usednotmanaged" report.', array(
        ':usednotmanaged' => l(t('Used not managed'), 'admin/reports/auditfiles/usednotmanaged'),
      )),
      'uri' => '',
      'usage' => '',
    );
  }
  else {
    $usage = '<ul>';
    $results = db_query("SELECT * FROM {file_usage} WHERE fid = {$file_id}");
    foreach ($results as $file_usage) {

      // Create the usages list.
      $used_by = $file_usage->module . ' ' . t('module');
      $used_in = l($file_usage->type . '/' . $file_usage->id, $file_usage->type . '/' . $file_usage->id);
      $times_used = $file_usage->count;
      $usage .= '<li>' . t('Used by: %used_by; Used in: %used_in; Times used: %times_used', array(
        '%used_by' => $used_by,
        '%used_in' => $used_in,
        '%times_used' => $times_used,
      )) . '</li>';
    }
    $usage .= '</ul>';

    // Create the data for displaying in the table.
    $row = array(
      'fid' => $file_id,
      'uri' => $file_managed->uri,
      'usage' => $usage,
    );
  }
  return $row;
}

/**
 * The following are helper functions.
 */

/**
 * Returns any report limiting settings.
 *
 * Returns the information that is needed to modify the report and display based
 * on any report limiting options that may have been set on the administrative
 * configuration settings page.
 *
 * @return array
 *   An associative array with these two values:
 *   - record_selection: A string representing record selection type, on which
 *     maximum_records is based.
 *   - maximum_records: An integer representing the total number of records to
 *     display on a report. (A value of 0 means there is no limit.)
 */
function _auditfiles_used_not_referenced_get_record_limiters() {
  $record_selection = variable_get('auditfiles_used_not_referenced_record_selection', 'normal');
  $maximum_records = 0;
  if ($record_selection == 'batch_sets') {
    $maximum_records = variable_get('auditfiles_report_options_batch_size', 0);
  }
  elseif ($record_selection != 'batched') {
    $maximum_records = variable_get('auditfiles_report_options_maximum_records', 0);
    if ($maximum_records > 0) {
      $record_selection = 'limited';
      variable_set('auditfiles_used_not_referenced_record_selection', 'limited');
    }
  }
  return array(
    'record_selection' => $record_selection,
    'maximum_records' => $maximum_records,
  );
}

/**
 * Returns the header to use for the display table.
 *
 * @return array
 *   The header to use.
 */
function _auditfiles_used_not_referenced_get_header() {
  return array(
    'fid' => array(
      'data' => t('File ID'),
      'field' => 'fu.fid',
    ),
    'uri' => array(
      'data' => t('File URI'),
    ),
    'usage' => array(
      'data' => t('Usages'),
    ),
  );
}

Functions

Namesort descending Description
auditfiles_used_not_referenced_form Generates the report.
auditfiles_used_not_referenced_form_submit Submit handler for the auditfiles_used_not_referenced_form form.
_auditfiles_used_not_referenced_batch_delete_create_batch Creates the batch for deleting files from the file_usage table.
_auditfiles_used_not_referenced_batch_delete_process_batch The batch process for deleting the file.
_auditfiles_used_not_referenced_batch_delete_process_file Deletes the specified file from the file_usage table.
_auditfiles_used_not_referenced_batch_display_create_batch Prepares the definition for the page display batch.
_auditfiles_used_not_referenced_batch_display_get_operations Configures the operations for the batch process.
_auditfiles_used_not_referenced_batch_display_process_operation The batch process for displaying the files.
_auditfiles_used_not_referenced_batch_finish_batch The function that is called when the batch is complete.
_auditfiles_used_not_referenced_batch_set_common_values Adds vaules to a batch definition that are common to all batches in the file.
_auditfiles_used_not_referenced_confirm_operation Presents a confimation form to verify the user wants to complete the action.
_auditfiles_used_not_referenced_get_file_data Retrieves information about an individual file from the database.
_auditfiles_used_not_referenced_get_file_list Retrieves the file IDs to operate on.
_auditfiles_used_not_referenced_get_header Returns the header to use for the display table.
_auditfiles_used_not_referenced_get_record_limiters Returns any report limiting settings.