You are here

variable_clean.module in Variable Cleanup 6

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

Allows you to remove variables not currently used.

File

variable_clean.module
View source
<?php

/**
 * @file
 * Allows you to remove variables not currently used.
 */

/**
 * Implementation of hook_menu()
 */
function variable_clean_menu() {
  $items['admin/settings/variable_clean'] = array(
    'title' => 'Variable Cleanup',
    'description' => 'Allows you to remove variables not currently used.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'variable_clean_form',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
  );
  return $items;
}

/**
 * Form builder for variable cleanup.
 *
 * @ingroup forms
 *
 * @see variable_clean_form_submit()
 */
function variable_clean_form(&$form_state) {
  _variable_clean_timeouts();

  // Confirmation form.
  if (empty($form_state['storage']['step']) || $form_state['storage']['step'] == 'confirm') {
    $form_state['storage']['step'] = 'confirm';
    return confirm_form(array(), t('Cleanup variables'), 'admin/settings', t('Are you sure you want to scan your codebase for all used variables (This may take a long time)?'), t('Scan codebase'), t('Cancel'));
  }

  // Scan results form.
  $form_state['storage']['step'] = 'scan';
  $code_lines = $not_in = array();
  $return_var = 0;
  _variable_clean_timeouts();

  // Compile a list of directories to scan.
  $code_directories = array(
    'update.php',
    'install.php',
    'includes' . DIRECTORY_SEPARATOR,
    'modules' . DIRECTORY_SEPARATOR,
    'profiles' . DIRECTORY_SEPARATOR,
    'themes' . DIRECTORY_SEPARATOR,
    'sites' . DIRECTORY_SEPARATOR . 'all' . DIRECTORY_SEPARATOR,
  );
  $site_directory = dirname($_SERVER['SCRIPT_FILENAME']) . DIRECTORY_SEPARATOR . conf_path() . DIRECTORY_SEPARATOR;
  foreach ((array) glob($site_directory . '*', GLOB_ONLYDIR) as $directory) {
    $sub_directory = str_replace($site_directory, '', $directory);
    if (!in_array($sub_directory, array(
      'files',
      'CVS',
      '.svn',
    ))) {
      $code_directories[] = conf_path() . DIRECTORY_SEPARATOR . $sub_directory . DIRECTORY_SEPARATOR;
    }
  }

  // Get a list of all lines of code using variables.
  chdir(dirname($_SERVER['SCRIPT_FILENAME']));
  foreach ($code_directories as $code_directory) {
    drupal_set_message(t('Scaning %directory.', array(
      '%directory' => $code_directory,
    )));
    exec('grep -rn "^[:space:]*[^/\\*]*variable_[g,s]et" ' . escapeshellarg($code_directory), $code_lines, $return_var);
  }
  drupal_set_message(t('Scaning complete.'));

  // If there's less than 50 instances, something went horribly wrong.
  $variable_count = count($code_lines);
  if (!$variable_count || $variable_count < 50) {
    $message = t('Only %variable_count variable uses were found in code. This could not possibly be correct. <pre>!dump</pre>', array(
      '%variable_count' => $variable_count,
      '!dump' => filter_xss_admin(var_export($code_lines, TRUE)),
    ));
    $form['error']['#value'] = '<p>' . $message . '</p>';
    return $form;
  }

  // Reduce the list of code to a list of variables.
  extract(_variable_clean_code_get_variables($code_lines));

  //  dpm($static_variables, '$static_variables');
  //  dpm($dynamic_variables, '$dynamic_variables');
  //  dpm($non_processable_variables, '$non_processable_variables');
  if ($non_processable_variables) {

    // Remove our test vars.
    foreach ($non_processable_variables as $key => $variable) {
      if (strpos($variable, 'variable_clean_test') !== FALSE) {
        unset($non_processable_variables[$key]);
      }
    }
    $form['non_processable_variables']['#value'] = '<p>' . t('The following code prevents an accurate determination of what variables are in use.  Proceed carefully! !variables', array(
      '!variables' => '<br />' . filter_xss_admin(theme_item_list($non_processable_variables)),
    )) . '</p>';
  }
  $form['variables'] = array(
    '#type' => 'checkboxes',
    '#title' => t('The following variables appear to be unused and could be deleted.  Do so at your own risk'),
    '#options' => array(),
  );

  // Get all variables in the DB that are not used as static variables.
  if ($static_variables) {
    foreach ($static_variables as $variable) {
      $not_in[] = "'" . db_escape_string($variable) . "'";
    }
    $result = db_query('SELECT name FROM {variable}
      WHERE name NOT IN(' . implode(',', $not_in) . ')
      ORDER BY name');
    while ($db_variable = db_fetch_object($result)) {

      // Is it a dynamic variable?
      foreach ($dynamic_variables as $variable) {
        if (strpos($db_variable->name, $variable) === 0) {
          continue 2;
        }
      }

      // We found an honest-to-goodness unused variable.
      $form['variables']['#options'][$db_variable->name] = $db_variable->name;
    }
  }
  else {
    $form['error']['#value'] = '<p>' . t('Error.  No variables were found in the code.') . '/<p>';
  }
  if ($form['variables']['#options']) {
    $form['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Delete'),
    );
  }
  else {
    unset($form['variables']);
    $form['error']['#value'] = '<p>' . t('All variables in the database are used in code.') . '</p>';
  }
  return $form;
}

/**
 * Form submit handler for variable cleanup.
 *
 * @ingroup Forms
 *
 * @see variable_clean_form()
 */
function variable_clean_form_submit($form, &$form_state) {

  // Confirmation complete.
  if ($form_state['storage']['step'] == 'confirm') {
    $form_state['storage']['step'] = 'scan';
    $form_state['rebuild'] = TRUE;
    return;
  }

  // Delete the variables.
  $deleted_variables = array();
  foreach ($form_state['values']['variables'] as $variable) {
    if ($variable) {
      variable_del($variable);
      $deleted_variables[] = $variable;
    }
  }
  drupal_set_message(t('The following variables have been removed from the database: !variables', array(
    '!variables' => '<br />' . filter_xss_admin(implode('<br />', $deleted_variables)),
  )));
  $form_state['storage']['step'] = 'confirm';
  $form_state['rebuild'] = TRUE;
}

/**
 * Reduce a list of code into a list of variables, both static and dynamic.
 *
 * @param array $code_lines
 *
 * @return mixed
 *  Array of three arrays: 'static_variables', 'dyamic_variables', and 'non_processable_variables'.
 */
function _variable_clean_code_get_variables($code_lines) {
  $static_variables = $dynamic_variables = $non_processable_variables = array();
  foreach ($code_lines as $line) {

    // Skip stuff in SVN.
    // @todo This could be done when we grep, but would require also using find.
    if (strpos($line, DIRECTORY_SEPARATOR . '.svn' . DIRECTORY_SEPARATOR) !== FALSE) {
      continue;
    }

    // Extract the variable name.
    $matches = array();
    if (preg_match_all('!variable_[g,s]et\\(([^\',"]*?[\',"](.+?)[\',"].*?),!', $line, $matches, PREG_SET_ORDER)) {
      foreach ($matches as $match) {

        // $match[1] is what is between ( and ,
        // $match[2] is what is enclosed in the first set of quotes
        $cleaned_match_1 = str_replace(array(
          '"',
          "'",
        ), '', $match[1]);

        // Test for really twisted syntax that we aren't going to even try to deal with.
        // ex. "foo_{$bar['baz']}"
        if (preg_match('![\',"][^\',"]*?{[^\',"]*?\\[!', $match[1])) {
          $non_processable_variables[] = $line;
        }
        elseif ($match[2] != $cleaned_match_1) {

          // If static portion is not at the beginning, we are screwed.
          if (strpos($cleaned_match_1, $match[2]) !== 0 || !$match[2]) {
            $non_processable_variables[] = $line;
          }
          else {
            $dynamic_variables[$match[2]] = $match[2];
          }
        }
        elseif (($dollar_position = strpos($match[2], '$')) !== FALSE) {

          // If the dollar is in position 0 we are screwed.
          if ($dollar_position === 0) {
            $non_processable_variables[] = $line;
          }
          else {
            $variable = str_replace('{', '', substr($match[2], 0, $dollar_position));
            if ($variable) {
              $dynamic_variables[$variable] = $variable;
            }
            else {
              $non_processable_variables[] = $line;
            }
          }
        }
        else {
          $static_variables[$match[2]] = $match[2];
        }
      }
    }
  }
  return array(
    'static_variables' => $static_variables,
    'dynamic_variables' => $dynamic_variables,
    'non_processable_variables' => $non_processable_variables,
  );
}

/**
 * Setup some timeouts and check for required stuff.
 */
function _variable_clean_timeouts() {

  // This may take a long time.  Set some timeouts in a way that shared hosting can handle.
  if (ini_get('safe_mode')) {
    drupal_set_message(t('You are running PHP in safe-mode.  If you have trouble with PHP timing-out before processing completes you will need to increase "max_execution_time" in php.ini. Note that the use of safe-mode is depricated and not recommended.'), 'error');
  }
  else {
    if (function_exists('set_time_limit')) {
      set_time_limit(300);
    }
    else {
      drupal_set_message(t('The set_time_limit() function does not exist.  If you have trouble with PHP timing-out before processing completes you will need to increase "max_execution_time" in php.ini.'), 'error');
    }
  }
  if (function_exists('memory_get_usage')) {
    ini_set('memory_limit', '256M');
  }
  else {
    drupal_set_message(t('The version of PHP is old and not compiled with --enable-memory-limit.  If you have trouble with PHP timing-out before processing completes you will need to increase "memory_limit" in php.ini. But you should really be using a modern version of PHP.'), 'error');
  }
  db_query('SET SESSION wait_timeout = 300');

  // Test for grep.
  $return_var = 0;
  $grep_out = array();
  exec('grep --version', $grep_out, $return_var);
  if ($return_var != 0) {
    drupal_set_message(t('This module requires the command line utility "grep".  The following error was received: !error', array(
      '!error' => $return_var . '<br />' . filter_xss_admin(implode('<br />', $grep_out)),
    )));
  }
}

Functions

Namesort descending Description
variable_clean_form Form builder for variable cleanup.
variable_clean_form_submit Form submit handler for variable cleanup.
variable_clean_menu Implementation of hook_menu()
_variable_clean_code_get_variables Reduce a list of code into a list of variables, both static and dynamic.
_variable_clean_timeouts Setup some timeouts and check for required stuff.