Skip to content
coder.module 34.7 KiB
Newer Older
Doug Green's avatar
Doug Green committed
<?php
// $Id$

/** @file
 * Developer Module that assists with code review and version upgrade that
 * supports a plug-in extensible hook system so contributed modules can
 * define additional review standards.
 *
 * Built-in support for:
 * - Drupal Coding Standards - http://drupal.org/node/318
 * - Handle text in a secure fashion - http://drupal.org/node/28984
 * - Converting 4.6.x modules to 4.7.x - http://drupal.org/node/22218
 * - Converting 4.7.x modules to 5.x - http://drupal.org/node/64279
 *
 * Credit also to dries:
 * - http://cvs.drupal.org/viewcvs/drupal/drupal/scripts/code-style.pl
Doug Green's avatar
Doug Green committed
 */ 

/**
 * Implementation of hook_init().
 */
function coder_init() {
  // hook init is called even on cached pages, but we don't want to
  // actually do anything in that case.
  if (!function_exists('drupal_get_path')) {
    return;
  }

  // Load all our module plug-ins
  global $_coder_coders;
  $path = drupal_get_path('module', 'coder') .'/includes';
  $files = drupal_system_listing('coder_.*\.inc$', $path, 'name', 0);
Doug Green's avatar
Doug Green committed
  foreach ($files as $file) {
    $_coder_coders[] = $file->name;
Doug Green's avatar
Doug Green committed
  }
}

/**
 * Get all of the code review modules
 */
function _coder_reviews() {
  $reviews = array();

  // get the review definitions from the include directory
  global $_coder_coders;
  if ($_coder_coders) {
    foreach ($_coder_coders as $coder) {
Doug Green's avatar
Doug Green committed
      if (function_exists($function)) {
        if ($review = call_user_func($function)) {
          $reviews = array_merge($reviews, $review);
        }
      }
    }
  }

  // get the contributed module review definitions
  if ($review = module_invoke_all('reviews')) {
    $reviews = array_merge($reviews, $review);
  }

  return $reviews;
}

/**
 * Implementation of hook_cron().
 */
function coder_cron() {
  if ($use_cache = variable_get('coder_cache', 1)) {
    // TODO: move some of the work here... is this really worth it?
  }
Doug Green's avatar
Doug Green committed
}

/**
 * Implementation of hook_perm().
 */
function coder_perm() {
  return array('view code review');
}

/**
 * Implementation of hook_menu().
 */
function coder_menu($may_cache = TRUE) {
Doug Green's avatar
Doug Green committed
  $items = array();

  if ($may_cache) {
    if (substr(VERSION, 0, 1) == '6') {
      $items['coder'] = array(
        'title' => t('Code review'),
        'page callback' => 'coder_page',
        'access arguments' => array('view code review'),
        'type' => MENU_NORMAL_ITEM,
      );
Doug Green's avatar
Doug Green committed
      $items['coder/settings'] = array(
        'title' => t('Selection Form'),
        'page callback' => 'coder_page',
        'access arguments' => array('view code review'),
        'type' => MENU_DEFAULT_LOCAL_TASK,
        'weight' => -2,
      );
      $items['coder/default'] = array(
        'title' => t('Default'),
        'page callback' => 'coder_page',
        'access arguments' => array('view code review'),
        'type' => MENU_LOCAL_TASK,
        'weight' => -1,
Doug Green's avatar
Doug Green committed
      );
      $items['coder/core'] = array(
        'title' => t('Core'),
        'page callback' => 'coder_page',
        'access arguments' => array('view code review'),
Doug Green's avatar
Doug Green committed
        'type' => MENU_LOCAL_TASK,
      );
      $items['coder/active'] = array(
        'title' => t('Active'),
        'page callback' => 'coder_page',
        'access arguments' => array('view code review'),
Doug Green's avatar
Doug Green committed
        'type' => MENU_LOCAL_TASK,
      );
      $items['coder/all'] = array(
        'title' => t('All'),
Doug Green's avatar
Doug Green committed
        'page callback' => 'coder_page',
        'access arguments' => array('view code review'),
        'type' => MENU_LOCAL_TASK,
        'weight' => 1,
      );
      $items['admin/settings/coder'] = array(
        'title' => t('Code review'),
        'description' => t('Select code review plugins and modules'),
        'page callback' => 'drupal_get_form',
        'page arguments' => array('coder_admin_settings'),
        'access arguments' => array('administer site configuration'),
      );
    }
    else {
      $items[] = array(
        'path' => 'coder',
        'title' => t('Code review'),
        'callback' => 'coder_page',
        'access' => user_access('view code review'),
        'type' => MENU_NORMAL_ITEM,
      );
Doug Green's avatar
Doug Green committed
      $items[] = array(
        'path' => 'coder/settings',
        'title' => t('Selection Form'),
        'callback' => 'coder_page',
        'access' => user_access('view code review'),
        'type' => MENU_DEFAULT_LOCAL_TASK,
        'weight' => -2,
      );
      $items[] = array(
        'path' => 'coder/default',
        'title' => t('Default'),
        'callback' => 'coder_page',
        'access' => user_access('view code review'),
        'type' => MENU_LOCAL_TASK,
        'weight' => -1,
Doug Green's avatar
Doug Green committed
      );
      $items[] = array(
        'path' => 'coder/core',
        'title' => t('Core'),
        'callback' => 'coder_page',
        'access' => user_access('view code review'),
Doug Green's avatar
Doug Green committed
        'type' => MENU_LOCAL_TASK,
      );
      $items[] = array(
        'path' => 'coder/active',
        'title' => t('Active'),
        'callback' => 'coder_page',
        'access' => user_access('view code review'),
Doug Green's avatar
Doug Green committed
        'type' => MENU_LOCAL_TASK,
      );
      $items[] = array(
        'path' => 'coder/all',
        'title' => t('All'),
Doug Green's avatar
Doug Green committed
        'callback' => 'coder_page',
        'access' => user_access('view code review'),
        'type' => MENU_LOCAL_TASK,
        'weight' => 1,
      );
      $items[] = array(
        'path' => 'admin/settings/coder',
        'title' => t('Code review'),
        'description' => t('Select code review plugins and modules'),
        'callback' => 'drupal_get_form',
        'callback arguments' => 'coder_admin_settings',
        'access' => user_access('administer site configuration'),
      );
    }
Doug Green's avatar
Doug Green committed
  }

  return $items;
}

/**
 * Implementation of hook_form_alter().
 */
function coder_form_alter($form_id, &$form) {
  if ($form_id == 'system_modules') {
    if (user_access('view code review')) {
      foreach ($form['name'] as $name => $data) {
        $form['name'][$name]['#value'] = l($data['#value'], "coder/$name");
Doug Green's avatar
Doug Green committed
/**
 * Helper functions for settings form
 */
function _coder_default_reviews() {
  return drupal_map_assoc(array('style', 'security'));
Doug Green's avatar
Doug Green committed
function _coder_settings_form($settings, &$system, &$files) {
Doug Green's avatar
Doug Green committed
  // add the javascript
  $path = drupal_get_path('module', 'coder');
  drupal_add_js($path .'/coder.js');
Doug Green's avatar
Doug Green committed

  // create the list of review options from the coder review plug-ins
  $reviews = _coder_reviews();
  foreach ($reviews as $name => $review) {
Doug Green's avatar
Doug Green committed
    $review_options[$name] = l($review['#title'], $review['#link']);
Doug Green's avatar
Doug Green committed
  }

  // what review standards should be applied
  $form['coder_reviews_group'] = array(
    '#type' => 'fieldset',
    '#title' => t('Reviews'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $form['coder_reviews_group']['coder_reviews'] = array(
    '#type' => 'checkboxes',
    '#options' => $review_options,
    '#description' => t('apply the checked coding reviews'),
Doug Green's avatar
Doug Green committed
    '#default_value' => $settings['coder_reviews'],
  );
  $form['coder_reviews_group']['coder_severity'] = array(
    '#type' => 'radios',
    '#options' => array(
      1 => 'minor (most)',
      5 => 'normal',
      9 => 'critical (fewest)'
    ),
    '#description' => t('show warnings at or above the severity warning level'),
    '#default_value' => $settings['coder_severity'],
Doug Green's avatar
Doug Green committed
  // get the modules and theme
  $sql = "SELECT name, filename, type, status FROM {system} WHERE type='module' OR type='theme' ORDER BY weight ASC, filename ASC";
Doug Green's avatar
Doug Green committed
  $result = db_query($sql);
Doug Green's avatar
Doug Green committed
  $system_modules = array();
  $system_themes = array();
  while ($system = db_fetch_object($result)) {
    $display_name = $system->name;
    if ($system->status) {
Doug Green's avatar
Doug Green committed
      $display_name .= t(' (active)');
Doug Green's avatar
Doug Green committed
      $system_active[$system->name] = $system->name;
Doug Green's avatar
Doug Green committed
    }
Doug Green's avatar
Doug Green committed
    if (_coder_is_drupal_core($system)) {
Doug Green's avatar
Doug Green committed
      $display_name .= t(' (core)');
Doug Green's avatar
Doug Green committed
      $system_core[$system->name] = $system->name;
Doug Green's avatar
Doug Green committed
    }
Doug Green's avatar
Doug Green committed
    if ($system->type == 'module') {
      $system_modules[$system->name] = $system->name;
    }
    else {
      $system_themes[$system->name] = $system->name;
    }
    $system_links[$system->name] = l($display_name, "coder/$system->name");
    $files[$system->name] = $system->filename;
Doug Green's avatar
Doug Green committed
  // display what to review options
Doug Green's avatar
Doug Green committed
  $form['coder_what'] = array(
Doug Green's avatar
Doug Green committed
    '#type' => 'fieldset',
Doug Green's avatar
Doug Green committed
    '#title' => t('What to review'),
Doug Green's avatar
Doug Green committed
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
Doug Green's avatar
Doug Green committed
  $form['coder_what']['coder_active_modules'] = array( // NOTE: should rename var
Doug Green's avatar
Doug Green committed
    '#type' => 'checkbox',
Doug Green's avatar
Doug Green committed
    '#default_value' => isset($settings['coder_active_modules']) ? $settings['coder_active_modules'] : 0,
Doug Green's avatar
Doug Green committed
    '#title' => t('active modules and themes'),
Doug Green's avatar
Doug Green committed
  );
Doug Green's avatar
Doug Green committed
  $form['coder_what']['coder_core'] = array(
Doug Green's avatar
Doug Green committed
    '#type' => 'checkbox',
Doug Green's avatar
Doug Green committed
    '#default_value' => isset($settings['coder_core']) ? $settings['coder_core'] : 0,
Doug Green's avatar
Doug Green committed
    '#title' => t('core files (php, modules, and includes)'),
Doug Green's avatar
Doug Green committed
  );
Doug Green's avatar
Doug Green committed
  $form['coder_what']['coder_includes'] = array(
Doug Green's avatar
Doug Green committed
    '#type' => 'checkbox',
    '#default_value' => $settings['coder_includes'],
Doug Green's avatar
Doug Green committed
    '#title' => t('include files (.inc and .php files)'),
Doug Green's avatar
Doug Green committed
  );
Doug Green's avatar
Doug Green committed
    $form['coder_what']['coder_cache'] = array(
      '#type' => 'checkbox',
      '#default_value' => $settings['coder_cache'],
      '#title' => t('use the experimental coder cache'),
    );
  }
Doug Green's avatar
Doug Green committed

  // display the modules in a fieldset
Doug Green's avatar
Doug Green committed
  $form['coder_what']['coder_modules'] = array(
Doug Green's avatar
Doug Green committed
    '#type' => 'fieldset',
    '#title' => t('Select Specific Modules'),
    '#collapsible' => TRUE,
Doug Green's avatar
Doug Green committed
    '#collapsed' => TRUE,
Doug Green's avatar
Doug Green committed
  );
  if (isset($settings['coder_all'])) {
    $modules = $system_modules;
  }
  elseif (isset($settings['coder_active_modules']) && $settings['coder_active_modules']) {
Doug Green's avatar
Doug Green committed
    if (isset($settings['coder_core']) && $settings['coder_core']) {
Doug Green's avatar
Doug Green committed
      $modules = array_intersect($system_active, $system_core);
      $modules = array_intersect($modules, $system_modules);
    }
    else {
      $modules = array_intersect($system_active, $system_modules);
    }
  }
Doug Green's avatar
Doug Green committed
  elseif (isset($settings['coder_core']) && $settings['coder_core']) {
Doug Green's avatar
Doug Green committed
    $modules = array_intersect($system_core, $system_modules);
  }
Doug Green's avatar
Doug Green committed
  elseif (isset($settings['coder_active_modules']) && $settings['coder_active_modules']) {
Doug Green's avatar
Doug Green committed
    $modules = array_intersect($system_active, $system_modules);
  }
  else {
Doug Green's avatar
Doug Green committed
    $modules = isset($settings['coder_modules']) && is_array($settings['coder_modules']) ? $settings['coder_modules'] : array();
Doug Green's avatar
Doug Green committed
  }

  // display the themes in a fieldset
  $form['coder_what']['coder_themes'] = array(
    '#type' => 'fieldset',
    '#title' => t('Select Specific Themes'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  if (isset($settings['coder_all'])) {
    $themes = $system_themes;
  }
  elseif (isset($settings['coder_active_modules']) && $settings['coder_active_modules']) {
Doug Green's avatar
Doug Green committed
    if (isset($settings['coder_core']) && $settings['coder_core']) {
Doug Green's avatar
Doug Green committed
      $themes = array_intersect($system_active, $system_core);
      $themes = array_intersect($themes, $system_themes);
Doug Green's avatar
Doug Green committed
    }
    else {
Doug Green's avatar
Doug Green committed
      $themes = array_intersect($system_active, $system_themes);
Doug Green's avatar
Doug Green committed
    }
  }
Doug Green's avatar
Doug Green committed
  elseif (isset($settings['coder_core']) && $settings['coder_core']) {
Doug Green's avatar
Doug Green committed
    $themes = array_intersect($system_core, $system_themes);
Doug Green's avatar
Doug Green committed
  }
Doug Green's avatar
Doug Green committed
  elseif (isset($settings['coder_active_modules']) && $settings['coder_active_modules']) {
Doug Green's avatar
Doug Green committed
    $themes = array_intersect($system_active, $system_themes);
Doug Green's avatar
Doug Green committed
  }
  else {
Doug Green's avatar
Doug Green committed
    $themes = isset($settings['coder_themes']) && is_array($settings['coder_themes']) ? $settings['coder_themes'] : array();
Doug Green's avatar
Doug Green committed
  }
Doug Green's avatar
Doug Green committed

  foreach ($system_links as $name => $link) {
Doug Green's avatar
Doug Green committed
    $classes = array();
Doug Green's avatar
Doug Green committed
    if (in_array($name, $system_active)) {
Doug Green's avatar
Doug Green committed
      $classes[] = "coder-active";
    }
Doug Green's avatar
Doug Green committed
    if (in_array($name, $system_core)) {
Doug Green's avatar
Doug Green committed
      $classes[] = "coder-core";
    }
Doug Green's avatar
Doug Green committed
    if (in_array($name, $system_themes)) {
      $type = 'theme';
      $default_value = isset($themes[$name]);
    }
    else {
      $type = 'module';
      $default_value = isset($modules[$name]);
    }
    $form['coder_what']["coder_${type}s"]["coder_${type}s-$name"] = array(
Doug Green's avatar
Doug Green committed
      '#type' => 'checkbox',
Doug Green's avatar
Doug Green committed
      '#title' => $link,
      '#default_value' => $default_value,
Doug Green's avatar
Doug Green committed
      '#attributes' => array('class' => implode(' ', $classes)),
    );
  }
Doug Green's avatar
Doug Green committed
  $system = array_merge($modules, $themes);
Doug Green's avatar
Doug Green committed
  return $form;
}

/**
 * Implementation of settings page for Drupal 5
 */
function coder_admin_settings() {
Doug Green's avatar
Doug Green committed
  $settings = _coder_get_default_settings();
Doug Green's avatar
Doug Green committed
  $form = _coder_settings_form($settings, $system, $files);
Doug Green's avatar
Doug Green committed
  $form['#submit']['coder_settings_form_submit'] = array();
  $form['#submit']['system_settings_form_submit'] = array();
  return system_settings_form($form);
}

function coder_settings_form_submit($form_id, &$form_values) {
Doug Green's avatar
Doug Green committed
  variable_set('coder_modules', _coder_settings_array($form_values, 'module'));
  variable_set('coder_themes', _coder_settings_array($form_values, 'theme'));
Doug Green's avatar
Doug Green committed
function _coder_settings_array(&$form_values, $type) {
  $typekey = "coder_{$type}s-";
  $typelen = strlen($typekey);
  $systems = array();
Doug Green's avatar
Doug Green committed
  foreach ($form_values as $key => $value) {
Doug Green's avatar
Doug Green committed
    if (substr($key, 0, $typelen) == $typekey) {
Doug Green's avatar
Doug Green committed
      if ($value == 1) {
Doug Green's avatar
Doug Green committed
        $system = substr($key, $typelen);
        $systems[$system] = 1;
Doug Green's avatar
Doug Green committed
      }
      unset($form_values[$key]);
    }
  }
Doug Green's avatar
Doug Green committed
  return $systems;
Doug Green's avatar
Doug Green committed
}

function coder_page_form_submit($form_id, $form_values) {
  // HELP: is there a better way to get these to coder_page_form()???
  return FALSE;
Doug Green's avatar
Doug Green committed
/**
 * Implementation of code review page
 */
function coder_page() {
Doug Green's avatar
Doug Green committed
  return drupal_get_form('coder_page_form');
}

function _coder_get_default_settings($args = '') {
  $settings['coder_reviews'] = variable_get('coder_reviews', _coder_default_reviews());
  $settings['coder_severity'] = variable_get('coder_severity', 5);
  $settings['coder_cache'] = variable_get('coder_cache', 1);
Doug Green's avatar
Doug Green committed

  // determine any options based on the passed in URL,
  switch ($args) {
    case '':
Doug Green's avatar
Doug Green committed
    case 'settings':
      $settings['coder_includes'] = 1;
Doug Green's avatar
Doug Green committed
      break;

    case 'active':
      $settings['coder_active_modules'] = 1;
      break;

    case 'core':
Doug Green's avatar
Doug Green committed
      $settings['coder_core'] = 1;
Doug Green's avatar
Doug Green committed
      $settings['coder_includes'] = 1;
      break;

    case 'all':
      $settings['coder_core'] = 1;
      $settings['coder_includes'] = 1;
      $settings['coder_all'] = 1;
      break;

Doug Green's avatar
Doug Green committed
    case 'default':
      $settings['coder_active_modules'] = variable_get('coder_active_modules', 1);
Doug Green's avatar
Doug Green committed
      $settings['coder_core'] = variable_get('coder_core', 0);
Doug Green's avatar
Doug Green committed
      $settings['coder_includes'] = variable_get('coder_includes', 0);
Doug Green's avatar
Doug Green committed
      $settings['coder_modules'] = variable_get('coder_modules', array());
      $settings['coder_themes'] = variable_get('coder_themes', array());
Doug Green's avatar
Doug Green committed
      break;

Doug Green's avatar
Doug Green committed
    default:
      $settings['coder_includes'] = 1;
Doug Green's avatar
Doug Green committed
      // TODO: does this need to go into coder_themes sometimes?
Doug Green's avatar
Doug Green committed
      $settings['coder_modules'] = array($args => $args);
      break;
  }
  return $settings;
}

function coder_page_form() {
  // HELP: is there a better way to get these from coder_page_form_submit()???
  $form_values = $_POST;
  if (isset($form_values['op'])) {
    $settings = $form_values;
Doug Green's avatar
Doug Green committed
    $settings['coder_modules'] = _coder_settings_array($form_values, 'module');
    $settings['coder_themes'] = _coder_settings_array($form_values, 'theme');
    drupal_set_title(t('Code review (submitted options)'));
Doug Green's avatar
Doug Green committed
  }
  else {
    $options = arg(1);
    $settings = _coder_get_default_settings($options);
    if ($options) {
      drupal_set_title(t('Code review (@options)', array('@options' => isset($options) ? $options : 'default options')));
    }
Doug Green's avatar
Doug Green committed
  }
Doug Green's avatar
Doug Green committed

  // get this once - list of the reviews to perform
  $reviews = array();
  $avail_reviews = _coder_reviews();
Doug Green's avatar
Doug Green committed
  $selected_reviews = $settings['coder_reviews'];
Doug Green's avatar
Doug Green committed
  foreach ($selected_reviews as $name => $checked) {
    if ($checked) {
      $reviews[$name] = $avail_reviews[$name];
    }
  }

Doug Green's avatar
Doug Green committed
  if ($coder_form = _coder_settings_form($settings, $system, $files)) {
Doug Green's avatar
Doug Green committed
    // add style sheet
    $path = drupal_get_path('module', 'coder');
    drupal_add_css($path .'/coder.css', 'module');
    // code review non-module core files
    $module_weight = 0;
Doug Green's avatar
Doug Green committed
    if (isset($settings['coder_core']) && $settings['coder_core']) {
      $coder_args = array(
        '#reviews' => $reviews,
        '#severity' => $settings['coder_severity'],
        '#filename' => $filename,
      );

      $form['core_php'] = array(
        '#type' => 'fieldset',
        '#title' => 'core (php)',
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
        '#weight' => ++ $module_weight,
      );
      $phpfiles = file_scan_directory('.', '.*\.php', array('.', '..', 'CVS'), 0, FALSE, 'name', 0);
      _coder_page_form_includes($form, $coder_args, 'core_php', $phpfiles, 2);

      $form['core_includes'] = array(
        '#type' => 'fieldset',
        '#title' => 'core (includes)',
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
        '#weight' => ++ $module_weight,
      );
      $includefiles = drupal_system_listing('.*\.inc$', 'includes', 'name', 0);
      _coder_page_form_includes($form, $coder_args, 'core_includes', $includefiles, 0);
    }

Doug Green's avatar
Doug Green committed
    // loop through the selected modules and themes
    if (isset($system)) {
Doug Green's avatar
Doug Green committed
      $dups = array(); // used to avoid duplicate includes
Doug Green's avatar
Doug Green committed
      foreach ($system as $name => $checked) {
Doug Green's avatar
Doug Green committed
        if ($checked) {
          // process this one file
          $filename = $files[$name];
          if (!$filename) {
Doug Green's avatar
Doug Green committed
            drupal_set_message(t('Code Review file for %module not found', array('%module' => $name)));
Doug Green's avatar
Doug Green committed
            continue;
          }
          $coder_args = array(
            '#reviews' => $reviews,
            '#severity' => $settings['coder_severity'],
            '#filename' => $filename,
          );
          $results = do_coder_reviews($coder_args);

          // output the results in a collapsible fieldset
          $form[$name] = array(
            '#type' => 'fieldset',
            '#title' => $filename,
            '#collapsible' => TRUE,
            '#collapsed' => TRUE,
            '#weight' => ++ $module_weight,
          );
          if (count($results) == 0) {
            $results[] = t('No Problems Found');
          }
          else {
            $form[$name]['#collapsed'] = FALSE;
          }
          $form[$name]['output'] = array(
            '#value' => theme('coder', $name, $filename, $results),
            '#weight' => -1,
          );

          // process the same directory include files
          if ($settings['coder_includes']) {
            // NOTE: convert to the realpath here so drupal_system_listing
            // doesn't return additional paths (i.e., try "module").
            $path = str_replace('\\', '/', dirname(realpath($filename)));
            $offset = strpos($path, dirname($filename));
            if (!isset($dups[$path])) {
Doug Green's avatar
Doug Green committed
              if (substr($filename, -7) == '.module') {
                $coder_args['#php_minor'] = 1;
              }
Doug Green's avatar
Doug Green committed
              $dups[$path] = 1;
              $includefiles = drupal_system_listing('.*\.(inc|php)$', $path, 'name', 0);
              _coder_page_form_includes($form, $coder_args, $name, $includefiles, $offset);
            }
Doug Green's avatar
Doug Green committed
    // prepend the settings form
    $form['settings'] = array(
      '#type' => 'fieldset',
Doug Green's avatar
Doug Green committed
      '#title' => t('Selection Form'),
Doug Green's avatar
Doug Green committed
      '#collapsible' => TRUE,
      '#collapsed' => isset($form),
Doug Green's avatar
Doug Green committed
      '#weight' => -1,
    );
    if ($form['settings']['#collapsed']) {
Doug Green's avatar
Doug Green committed
      $form['settings']['#prefix'] = t('<div>Use the Selection Form to select options for this code review, or change the <a href="@settings">Default Settings</a> and use the <a href="@default">Default</a> tab above.</div>', array('@settings' => url('admin/settings/coder'), '@default' => url('coder/default')));
Doug Green's avatar
Doug Green committed
    $form['settings'][] = $coder_form;
    $form['settings']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Submit'),
    );
Doug Green's avatar
Doug Green committed
    $form['#action'] = url('coder');
Doug Green's avatar
Doug Green committed
  }
Doug Green's avatar
Doug Green committed
  return $form;
function _coder_page_form_includes(&$form, $coder_args, $name, $files, $offset) {
Doug Green's avatar
Doug Green committed
  $coder_args['#name'] = $name;
  foreach ($files as $file) {
    $filename = drupal_substr($file->filename, $offset);
    $coder_args['#filename'] = $filename;
    $results = do_coder_reviews($coder_args);

    // output the results in a collapsible fieldset
    $form[$name][$filename] = array(
      '#type' => 'fieldset',
      '#title' => $filename,
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#weight' => ++ $weight,
    );
    if (count($results) == 0) {
      $results[] = t('No Problems Found');
    }
    else {
      $form[$name][$filename]['#collapsed'] = FALSE;
      $form[$name]['#collapsed'] = FALSE;
    }
    $form[$name][$filename]['output'] = array(
      '#value' => theme('coder', $name, $filename, $results),
    );
  }
}

function _coder_modified() {
  static $_coder_mtime;
  if (!isset($_coder_mtime)) {
    $path = drupal_get_path('module', 'coder');
    $includefiles = drupal_system_listing('.*\.(inc|module)$', $path, 'name', 0);
    $_coder_mtime = 0;
    foreach ($includefiles as $file) {
      $mtime = filemtime(realpath($file->filename));
      if ($mtime > $_coder_mtime) {
        $_coder_mtime = $mtime;
      }
    }
  }
  return $_coder_mtime;
}

function do_coder_reviews($coder_args) {
  // the cache is still experimental, so users must enable it
  if ($use_cache = variable_get('coder_cache', 1)) {
    // cache the results because:
    $cache_key = 'coder:'.  implode(':', array_keys($coder_args['#reviews'])) .  $coder_args['#severity'] .':'. $coder_args['#filename'];
    $cache_mtime = filemtime(realpath($coder_args['#filename']));
    if ($cache_serialized_results = cache_get($cache_key)) {
      $cache_results = unserialize($cache_serialized_results->data);

      if ($cache_results['mtime'] == $cache_mtime && _coder_modified() < $cache_serialized_results->created) {
  // skip php include files when the user requested severity is above minor
Doug Green's avatar
Doug Green committed
  if ($coder_args['#php_minor'] && drupal_substr($coder_args['#filename'], -4) == '.php') {
    if ($coder_args['#severity'] > 1) {
      return $results;
    }
  }

  // read the file
  if (_coder_read_and_parse_file($coder_args)) {
    // do all of the code reviews
    foreach ($coder_args['#reviews'] as $review) {
      if ($result = do_coder_review($coder_args, $review)) {
        $results += $result;
      }
    }

Doug Green's avatar
Doug Green committed
    ksort($results, SORT_NUMERIC);
Doug Green's avatar
Doug Green committed
    _coder_error_msg($results, t('Could not read the file'), 'critical');
  // save the results in the cache
  if ($use_cache) {
    $cache_results = array(
      'mtime' => $cache_mtime,
      'results' => $results,
    );
    cache_set($cache_key, 'cache', serialize($cache_results));
  }

  return $results;
}

function _coder_read_and_parse_file(&$coder_args) {
Doug Green's avatar
Doug Green committed
  // get the path to the module file
  if ($filepath = realpath($coder_args['#filename'])) {
Doug Green's avatar
Doug Green committed
    // read the file
    $content = file_get_contents($filepath);
    $content_length = drupal_strlen($content);

    // parse the file:
    // - strip comments
    // - strip quote content // - strip stuff not in php // - break into lines
    $lineno = 0;
    for ($pos = 0; $pos < $content_length; $pos ++) {
      // get the current character
      $char = $content[$pos];
      if ($char == "\n") {
        if ($in_comment == '/') { // end C++ style comments on newline
          unset($in_comment);
        }
        // assume that html inside quotes doesn't span newlines
        unset($in_quote_html);
        // remove blank lines now, so we avoid processing them over-and-over
        if (trim($all_lines[$lineno]) == '') {
          unset($all_lines[$lineno]);
        if (trim($php_lines[$lineno]) == '') {
          unset($php_lines[$lineno]);
        }
        if (trim($html_lines[$lineno]) == '') {
          unset($html_lines[$lineno]);
        $beginning_of_line = 1;
      $all_lines[$lineno] .= $char;

      if ($in_php) {
        // look for the ending php tag which tags precedence over everything
        if ($char == '?' && $content[$pos + 1] == '>') {
          unset($char);
          unset($in_php);
          $all_lines[$lineno] .= '>';
        // when in a quoted string, look for the trailing quote
        // strip characters in the string, replacing with '' or ""
        elseif ($in_quote) {
          if ($in_backslash) {
            unset($in_backslash);
          }
          elseif ($char == '\\') {
            $in_backslash = '\\';
          }
          elseif ($char == $in_quote && !$in_backslash) {
            unset($in_quote);
          }
          elseif ($char == '<') {
            $in_quote_html = '>';
          }
          if ($in_quote && $in_quote_html) {
            $html_lines[$lineno] .= $char;
          }
          if ($char == $in_quote_html) {
            unset($in_quote_html);
          }
          unset($char); // NOTE: trailing char output with starting one
        }
        elseif ($in_heredoc) {
          if ($beginning_of_line && $char == $in_heredoc[0] && substr($content, $pos, $in_heredoc_length) == $in_heredoc) {
            $all_lines[$lineno] .= substr($content, $pos + 1, $in_heredoc_length - 1);
            unset($in_heredoc);
            $pos += $in_heredoc_length;
          }
          elseif ($char == '<') {
            $in_heredoc_html = '>';
          }
          if ($in_heredoc && $in_heredoc_html) {
            $html_lines[$lineno] .= $char;
          }
          if ($char == $in_heredoc_html) {
            unset($in_heredoc_html);
          }
          unset($char);
        }

        // when in a comment look for the trailing comment
        elseif ($in_comment) {
          if ($in_comment == '*' && $char == '*' && $content[$pos + 1] == '/') {
            unset($in_comment);
            $all_lines[$lineno] .= '/';
            $pos ++;
          }
          unset($char); // don't add comments to php output
          switch ($char) {
            case '\'':
            case '"':
              if ($content[$pos - 1] != '\\') {
                $php_lines[$lineno] .= $char;
                $in_quote = $char;
              }
              break;

            case '/':
              $next_char = $content[$pos + 1];
              if ($next_char == '/' || $next_char == '*') {
                unset($char);
                $in_comment = $next_char;
                $all_lines[$lineno] .= $next_char;

            case '<':
              if ($content[$pos + 1] == '<' && $content[$pos + 2] == '<') {
                unset($char);
                $all_lines[$lineno] .= '<<';

                // get the heredoc word
                // read until the end-of-line
                for ($pos += 3; $pos < $content_length; $pos ++) {
                  $char = $content[$pos];
                  if ($char == "\n") {
                    $pos --;
                    if (preg_match('/^\s+(\w+)/', $heredoc, $match)) {
                      $in_heredoc = $match[1];
                      $in_heredoc_length = drupal_strlen($in_heredoc);
                    }
                    break;
                  }
                  $all_lines[$lineno] .= $char;
                  $heredoc .= $char;
                }
                unset($heredoc);

                // replace heredoc's with an empty string
                $php_lines[$lineno] .= "''";
                unset($char);
              }
              break;
        if (isset($char)) {
          $php_lines[$lineno] .= $char;
      else {
        switch ($char) {
          case '<':
            if ($content[$pos + 1] == '?') {
              if ($content[$pos + 2] == ' ') {
                $in_php = 1;
                $all_lines[$lineno] .= '? ';
                $pos += 2;
              }
              elseif (substr($content, $pos + 2, 3) == 'php') {
                $in_php = 1;
                $all_lines[$lineno] .= '?php';
                $pos += 4;
              }
              break;
            }
            // FALTHROUGH
          default:
            $html_lines[$lineno] .= $char;
            break;
        }
    // add the files lines to the arguments
    $coder_args['#all_lines'] = $all_lines;
    $coder_args['#php_lines'] = $php_lines;
    $coder_args['#html_lines'] = $html_lines;
    return 1;
Doug Green's avatar
Doug Green committed
function _coder_severity($severity_name, $default_value = 5) {
  // NOTE: implemented this way in hopes that it is faster than a php switch
  if (!isset($severity_names)) {
    $severity_names = array(
      'minor' => 1,
      'normal' => 5,
      'critical' => 9,
    );
  }
  if (isset($severity_names[$severity_name])) {
    return $severity_names[$severity_name];
  }
  return $default_value;
}

function _coder_severity_name($coder_args, $review, $rule) {
  // NOTE: warnings in php includes are suspicious because
  // php includes are frequently 3rd party products
Doug Green's avatar
Doug Green committed
  if ($coder_args['#php_minor'] && substr($coder_args['#filename'], -4) == '.php') {
Doug Green's avatar
Doug Green committed
    return 'minor';
  }

  // get the severity as defined by the rule
  if (isset($rule['#severity'])) {
    return $rule['#severity'];
  }

  // if it's not defined in the rule, then it can be defined by the review
  if (isset($review['#severity'])) {
    return $review['#severity'];
  }

  // use the default
  return 'normal';
}

function do_coder_review($coder_args, $review) {
Doug Green's avatar
Doug Green committed
  $results = array();
  if ($review['#rules']) {
Doug Green's avatar
Doug Green committed
    // get the review's severity, used when the rule severity is not defined
    $default_severity = _coder_severity($review['#severity']);

    foreach ($review['#rules'] as $rule) {
      // perform the review if above the user requested severity
      $severity = _coder_severity($rule['#severity'], $default_severity);
      if ($severity >= $coder_args['#severity']) {
        if (isset($rule['#original'])) { // deprecated
          $lines = $coder_args['#all_lines'];
        }
        elseif (isset($rule['#source'])) { // all, html, comment, or php
          $source = '#'. $rule['#source'] .'_lines';
          $lines = $coder_args[$source];
        }
        else {
          $lines = $coder_args['#php_lines'];
        }
        if ($lines) {
          switch ($rule['#type']) {
            case 'regex':
              do_coder_review_regex($coder_args, $review, $rule, $lines, $results);
              break;
            case 'grep':
              do_coder_review_grep($coder_args, $review, $rule, $lines, $results);
              break;
            case 'callback':
              do_coder_review_callback($coder_args, $review, $rule, $lines, $results);
              break;
          }
Doug Green's avatar
Doug Green committed
      }
    }
  }
  return $results;
}
Doug Green's avatar
Doug Green committed

Doug Green's avatar
Doug Green committed
function do_coder_review_regex(&$coder_args, $review, $rule, $lines, &$results) {
Doug Green's avatar
Doug Green committed
  if ($regex = $rule['#value']) {
    $regex = '/'. $regex .'/i';
Doug Green's avatar
Doug Green committed
    foreach ($lines as $lineno => $line) {
      if (preg_match($regex, $line, $matches)) {
        // don't match some regex's
        if ($not = $rule['#not']) {
          foreach ($matches as $match) {
            if (preg_match('/'. $not .'/i', $match)) {
        $line = $coder_args['#all_lines'][$lineno];
Doug Green's avatar
Doug Green committed
        $severity_name = _coder_severity_name($coder_args, $review, $rule);
        _coder_error($results, $rule, $severity_name, $lineno, $line);
Doug Green's avatar
Doug Green committed
function _coder_error(&$results, $rule, $severity_name, $lineno = -1, $line = '', $original = '') {
  if (isset($rule['#warning_callback'])) {
    if (function_exists($rule['#warning_callback'])) {