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

Doug Green's avatar
Doug Green committed
 * 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
 * - Converting 5.x modules to 6.x - http://drupal.org/node/114774
 * - Comment Standards - http://drupal.org/node/1354
 * - SQL coding conventions - http://drupal.org/node/2497
 *
 * Credit also to dries:
 * - http://cvs.drupal.org/viewcvs/drupal/drupal/scripts/code-style.pl
Doug Green's avatar
Doug Green committed
 */
Doug Green's avatar
Doug Green committed

/**
 * Implementation of hook_help().
Doug Green's avatar
Doug Green committed
 */
Doug Green's avatar
Doug Green committed
function coder_help($section) {
  switch ($section) {
    case 'coder#disclaimer':
      return t('Coder provides helpful hints without false positives, but offers no guarantee for creating good code.  You are the final arbitrar.  If in doubt, read the Drupal documentation (see review links below and <a href="@api">api.drupal.org</a>).', array('@api' => 'http://api.drupal.org'));
    default :
      return;
Doug Green's avatar
Doug Green committed
  }
}
Doug Green's avatar
Doug Green committed
/**
 * Get all of the code review modules, including contributions.
Doug Green's avatar
Doug Green committed
 */
function _coder_reviews() {
  return module_invoke_all('reviews');
}
function coder_reviews() {
  global $_coder_reviews;
  if (!isset($_coder_reviews)) {
    $_coder_reviews = array();
    $path = drupal_get_path('module', 'coder') .'/includes';
    $files = drupal_system_listing('coder_.*\.inc$', $path, 'filename', 0);
    foreach ($files as $file) {
      require_once('./'. $file->filename);
      $function = $file->name .'_reviews';
Doug Green's avatar
Doug Green committed
      if (function_exists($function)) {
        if ($review = call_user_func($function)) {
          $_coder_reviews = array_merge($_coder_reviews, $review);
Doug Green's avatar
Doug Green committed
}

/**
 * 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', 'view code review all');
Doug Green's avatar
Doug Green committed
}

/**
 * Implementation of hook_menu().
 */
function coder_menu($may_cache = true) {
Doug Green's avatar
Doug Green committed
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'coder',
      'title' => t('Code review'),
      'callback' => 'coder_page',
      'access' => user_access('view code review'),
      'type' => MENU_NORMAL_ITEM,
    );
    $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,
    );
    $items[] = array(
      'path' => 'coder/core',
      'title' => t('Core'),
      'callback' => 'coder_page',
      'access' => user_access('view code review'),
      'type' => MENU_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'coder/active',
      'title' => t('Active'),
      'callback' => 'coder_page',
      'access' => user_access('view code review'),
      'type' => MENU_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'coder/all',
      'title' => t('All'),
      'callback' => 'coder_page',
      'access' => user_access('view code review all'),
      '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().
 *
 * Modify the module display view by adding a Coder Review link to every
 * module description.
 */
function coder_form_alter($form_id, &$form) {
  if ($form_id == 'system_modules' && $form['#base'] != 'confirm_form') {
    if (user_access('view code review')) {
      $path = drupal_get_path('module', 'coder');
      drupal_add_css($path .'/coder.css', 'module');
      foreach ($form['name'] as $name => $data) {
        $form['description'][$name]['#value'] = $form['description'][$name]['#value'] .'('. l(t('Code Review'), "coder/$name") .')';
Doug Green's avatar
Doug Green committed
/**
 * Helper functions for settings form.
Doug Green's avatar
Doug Green committed
 */
function _coder_default_reviews() {
  return drupal_map_assoc(array('style', 'sql', 'comment', 'security'));
/**
 * Build settings form API array for coder.
 *
 * Generates a form with the default reviews and default modules/themes to
 * run Coder on.
 *
 * @note
 *   Actual forms may have additional sections added to them, this
 *   is simply a base.
 *
 * @param $settings
 *   Settings array for coder in the format of _coder_get_default_settings().
 * @param $system
 *   Array of module and theme information, in form string theme/module
 *   name => boolean TRUE if checked by coder already.
 * @param $files
 *   Associative array of files, in form string theme/module name => string
 *   filename to check.
 * @return
 *   Array for form API for the settings box.
 */
Doug Green's avatar
Doug Green committed
function _coder_settings_form($settings, &$system, &$files) {
Doug Green's avatar
Doug Green committed
  $path = drupal_get_path('module', 'coder');
  drupal_add_js($path .'/coder.js');
  // Create the list of review options from the coder review plug-ins.
  // Maintain a secondary list based on #title only, to make sorting possible.
Doug Green's avatar
Doug Green committed
  $reviews = _coder_reviews();
  foreach ($reviews as $name => $review) {
    $review_options[$name] = isset($review['#link']) ? l($review['#title'], $review['#link']) : $review['#title'];
    if (isset($review['#description'])) {
      $review_options[$name] .= ' ('. $review['#description'] .')';
    }
    $review_sort[$name] = $review['#title'];
  asort($review_sort);
  foreach ($review_sort as $name => $review) {
    $review_sort[$name] = $review_options[$name];
  }

Doug Green's avatar
Doug Green committed
  $form['coder_reviews_group'] = array(
    '#type' => 'fieldset',
    '#title' => t('Reviews'),
    '#collapsible' => true,
    '#collapsed' => false,
Doug Green's avatar
Doug Green committed
  );
  $form['coder_reviews_group']['coder_reviews'] = array(
    '#type' => 'checkboxes',
Doug Green's avatar
Doug Green committed
    '#description' => t('apply the checked coding reviews'),
Doug Green's avatar
Doug Green committed
    '#default_value' => $settings['coder_reviews'],
  );
Doug Green's avatar
Doug Green committed
  $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
  );
Doug Green's avatar
Doug Green committed

  $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
  }
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'),
    '#collapsible' => true,
    '#collapsed' => false,
Doug Green's avatar
Doug Green committed
  );
  // NOTE: Should rename var.
  $form['coder_what']['coder_active_modules'] = array(
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'],
  // 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,
    '#collapsed' => true,
    'checkboxes' => array(
      '#theme' => 'coder_checkboxes',
    ),
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
  $form['coder_what']['coder_themes'] = array(
    '#type' => 'fieldset',
    '#title' => t('Select Specific Themes'),
    '#collapsible' => true,
    '#collapsed' => true,
    'checkboxes' => array(
      '#theme' => 'coder_checkboxes',
    ),
Doug Green's avatar
Doug Green committed
  );
  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)) {
      $classes[] = 'coder-active';
Doug Green's avatar
Doug Green committed
    }
Doug Green's avatar
Doug Green committed
    if (in_array($name, $system_core)) {
Doug Green's avatar
Doug Green committed
    }
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"]['checkboxes']["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;
}

/**
 * Format checkbox field into columns.
 *
 * @param $form
 *   Form sub-array to render, usually supplied as callback.
 */
function theme_coder_checkboxes($form) {
  $total = 0;
  foreach ($form as $element_id => $element) {
    if ($element_id[0] != '#') {
      $total ++;
    }
  }
  $total = (int) (($total % 3) ? (($total + 2) / 3) : ($total / 3));
  $pos = 0;
  $rows = array();
  foreach ($form as $element_id => $element) {
    if ($element_id[0] != '#') {
      $pos ++;
      $row = $pos % $total;
      $col = $pos / $total;
      if (!isset($rows[$row])) {
        $rows[$row] = array();
      }
      $rows[$row][$col] = drupal_render($element);
    }
  }
  return theme('table', array(), $rows);
}

 * 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);
}

/**
 * Callback function for settings page in Drupal 5.
 */
Doug Green's avatar
Doug Green committed
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'));
/**
 * Generate settings array for either modules or themes.
 *
 * @param $form_values
 *   Form array passed to submit function (note: entries that are processed.
 *   are removed for efficiency's sake).
 * @param $type
 *   String type to generate settings for, either 'module' or 'theme'.
 * @return
 *   Settings lookup array in form module/theme name => 1
 */
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
/**
 * Implementation of code review page.
Doug Green's avatar
Doug Green committed
 */
function coder_page() {
  $output  = '<div class="coder-disclaimer">'. coder_help('coder#disclaimer') .'</div>';
  $output .= drupal_get_form('coder_page_form');
  return $output;
/**
 * Returns a active settings array for coder.
 *
 * @note
 *   The name is a misnomer, but is a largely correct characterization
 *   for most of Coder's settings as the variables usually do not exist.
 *
 * @param $args
 *   String settings argument, can be 'settings', 'active', 'core', 'all'
 *   and 'default'.
 * @return
 *   Associative array of settings in form setting name => setting value.
 */
function _coder_get_default_settings($args = 'default') {
Doug Green's avatar
Doug Green committed
  $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.
Doug Green's avatar
Doug Green committed
  switch ($args) {
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;
}

/**
 * Implementation of hook_form().
 *
 * Implements coder's main form, in which a user can select reviews and
 * modules/themes to run them on.
 */
function coder_page_form($form_values = null) {
Doug Green's avatar
Doug Green committed
  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
  }
  // Get this once - list of the reviews to perform.
Doug Green's avatar
Doug Green committed
  $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
    $path = drupal_get_path('module', 'coder');
    drupal_add_css($path .'/coder.css', 'module');
    // Code review non-module core files.
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,
      $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,
      $includefiles = drupal_system_listing('.*\.inc$', 'includes', 'filename', 0);
      _coder_page_form_includes($form, $coder_args, 'core_includes', $includefiles, 0);
    }

    // Loop through the selected modules and themes.
Doug Green's avatar
Doug Green committed
    if (isset($system)) {
      // Used to avoid duplicate includes.
      $dups = array();
Doug Green's avatar
Doug Green committed
      foreach ($system as $name => $checked) {
Doug Green's avatar
Doug Green committed
        if ($checked) {
Doug Green's avatar
Doug Green committed
          $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);
          $stats[$filename] = $results['#stats'];
          unset($results['#stats']);
Doug Green's avatar
Doug Green committed

          // Output the results in a collapsible fieldset.
Doug Green's avatar
Doug Green committed
          $form[$name] = array(
            '#type' => 'fieldset',
            '#title' => $filename,
            '#collapsible' => true,
            '#collapsed' => true,
Doug Green's avatar
Doug Green committed
            '#weight' => ++ $module_weight,
          );
Doug Green's avatar
Doug Green committed
            $results[] = t('No Problems Found');
          }
          else {
            $form[$name]['#collapsed'] = false;
Doug Green's avatar
Doug Green committed
          }
          $form[$name]['output'] = array(
            '#value' => theme('coder', $name, $filename, $results),
            '#weight' => -1,
          );

          // Process the same directory include files.
Doug Green's avatar
Doug Green committed
          if ($settings['coder_includes']) {
            // NOTE: Convert to the realpath here so drupal_system_listing
Doug Green's avatar
Doug Green committed
            // 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|install|schema)$', $path, 'filename', 0);
              $stats[$filename]['#includes'] = _coder_page_form_includes($form, $coder_args, $name, $includefiles, $offset);
Doug Green's avatar
Doug Green committed
            }
Doug Green's avatar
Doug Green committed
      }
      if (count($stats)) {
        $summary = array('files' => 0, 'minor' => 0, 'normal' => 0, 'critical' => 0);
        foreach ($stats as $stat) {
          if (isset($stat['#includes'])) {
            foreach ($stat['#includes'] as $includestat) {
              $summary['files'] ++;
              $summary['minor'] += $includestat['minor'];
              $summary['normal'] += $includestat['normal'];
              $summary['critical'] += $includestat['critical'];
            }
          }
          $summary['files'] ++;
        }
        $display = array();
        $display[] = t('Coder found @count projects', array('@count' => count($stats)));
        $display[] = t('@count files', array('@count' => $summary['files']));
        foreach (array('critical', 'normal', 'minor') as $severity_name) {
          if ($summary[$severity_name] > 0) {
            $display[] = t('@count %severity_name warnings', array('@count' => $summary[$severity_name], '%severity_name' => $severity_name));
          }
        }
        drupal_set_message(implode(', ', $display));
      }
Doug Green's avatar
Doug Green committed
    $form['settings'] = array(
      '#type' => 'fieldset',
Doug Green's avatar
Doug Green committed
      '#title' => t('Selection Form'),
      '#collapsed' => isset($form),
Doug Green's avatar
Doug Green committed
      '#weight' => -1,
    );
    if ($form['settings']['#collapsed']) {
      $form['settings']['#prefix'] = t('<div class="coder-settings">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'),
    );
    $form['#multistep'] = true;
    $form['#redirect'] = false;
Doug Green's avatar
Doug Green committed
  }
Doug Green's avatar
Doug Green committed
  return $form;
/**
 * Add results to form array for display on form page.
 *
 * @param $form
 *   Form array variable to be modified.
 * @param $coder_args
 *   Coder settings, see do_coder_reviews() for details.
 * @param $name
 *   Name of form element.
 * @param $files
 *   Array of file objects to check and display the results of, see
 *   file_scan_directory().
 * @param $offset
 *   Integer offset to munge filenames with.
 * @return
 *   Statistics array in form: string filename => array value of
 *   '#stats' from do_coder_reviews().
 */
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);
    $stats[$filename] = $results['#stats'];
    unset($results['#stats']);
    // Output the results in a collapsible fieldset.
    $form[$name][$filename] = array(
      '#type' => 'fieldset',
      '#title' => $filename,
      '#collapsible' => true,
      '#collapsed' => true,
      $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),
    );
  }
/**
 * Return last modification timestamp of coder and all of its dependencies.
 */
function _coder_modified() {
  static $_coder_mtime;
  if (!isset($_coder_mtime)) {
    $path = drupal_get_path('module', 'coder');
    $includefiles = drupal_system_listing('.*\.(inc|module)$', $path .'/includes', 'filename', 0);
    $_coder_mtime = filemtime(realpath($path .'/coder.module'));
    foreach ($includefiles as $file) {
      $mtime = filemtime(realpath($file->filename));
      if ($mtime > $_coder_mtime) {
        $_coder_mtime = $mtime;
      }
    }
  }
  return $_coder_mtime;
}

/**
 * Perform batch coder reviews for multiple files.
 *
 * @param $coder_args
 *   Array of coder arguments, valid arguments are:
 *     - '#reviews' => array list of reviews to perform, see _coder_reviews();
 *     - '#severity' => integer magic number, see constants SEVERITY_*;
 *     - '#filename' => string filename to check,
 * @return
 *   Array of results, in form:
 *     - '#stats' => Array with error counts for all severities, in form
 *         'minor' => integer count, 'normal' => integer count;
 *         'critical' => integer count;
 *     - integer ID => HTML error for display.
 */
function do_coder_reviews($coder_args) {
  if ($use_cache = variable_get('coder_cache', 1)) {
    // Load the cached results if they exists.
    $cache_key = 'coder:'.  implode(':', array_keys($coder_args['#reviews'])) .  $coder_args['#severity'] .':'. $coder_args['#filename'];
    $cache_mtime = filemtime(realpath($coder_args['#filename']));
    if ($cache_results = cache_get($cache_key)) {
      if ($cache_results->data['mtime'] == $cache_mtime && ($x = _coder_modified()) < $cache_results->created) {
        return unserialize($cache_results->data['results']);
  $results = array('#stats' => array('minor' => 0, 'normal' => 0, 'critical' => 0));
  // Skip php include files when the user requested severity is above minor.
  if (isset($coder_args['#php_minor']) && drupal_substr($coder_args['#filename'], -4) == '.php') {
    if ($coder_args['#severity'] > 1) {
      return $results;
    }
  }

  if (_coder_read_and_parse_file($coder_args)) {
    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');
  if ($use_cache) {
    $cache_results = array(
      'mtime' => $cache_mtime,
      'results' => $results,
    );
    cache_set($cache_key, 'cache', serialize($cache_results));
  }

/**
 * Parse and read a file into a format easy to validate.
 *
 * @param $coder_args
 *   Coder arguments array variable to add file lines of code (with
 *   trailing newlines. The following array indices are added: '#all_lines',
 *   '#php_lines', '#allphp_lines', '#html_lines', '#quote_lines',
 *   '#doublequote_lines', '#comment_lines'. Their names should be
 *   self explanatory.
 * @return
 *   Integer 1 if success.
 */
function _coder_read_and_parse_file(&$coder_args) {
  if ($filepath = realpath($coder_args['#filename'])) {
    $content = file_get_contents($filepath) ."\n";
    $content_length = drupal_strlen($content);

    $in_comment = 0;
    $beginning_of_line = 0;
    $in_php = 0;
Doug Green's avatar
Doug Green committed
    $in_allphp = 0;
    $in_quote_html = 0;
    $in_backslash = 0;
    $in_quote = 0;
    $in_heredoc = 0;
    $in_heredoc_html = '';
    $heredoc = '';
    $all_lines = array();
    $php_lines = array();
Doug Green's avatar
Doug Green committed
    $allphp_lines = array();
    $html_lines = array();
    $quote_lines = array();
    $doublequote_lines = array();
    $comment_lines = array();
    $this_all_lines = '';
    $this_php_lines = '';
Doug Green's avatar
Doug Green committed
    $this_allphp_lines = '';
    $this_html_lines = '';
    $this_quote_lines = '';
    $this_doublequote_lines = '';
    $this_comment_lines = '';

    // 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 ++) {
      $char = $content[$pos];
      if ($char == "\n") {
        if ($in_comment && $in_comment == '/') { // End C++ style comments on newline.
        // Assume that html inside quotes doesn't span newlines.
        // Remove blank lines now, so we avoid processing them over-and-over.
        if ($this_all_lines != '') {
          if (trim($this_all_lines) != '') {
            $all_lines[$lineno] = $this_all_lines;
          }
          if (trim($this_php_lines) != '') {
            $php_lines[$lineno] = $this_php_lines;
          }
Doug Green's avatar
Doug Green committed
          if (trim($this_allphp_lines) != '') {
            $allphp_lines[$lineno] = $this_allphp_lines;
          }
          if (trim($this_html_lines) != '') {
            $html_lines[$lineno] = $this_html_lines;
          }
          if (trim($this_quote_lines) != '') {
            $quote_lines[$lineno] = $this_quote_lines;
          }
          if (trim($this_doublequote_lines) != '') {
            $doublequote_lines[$lineno] = $this_doublequote_lines;
          }
          if (trim($this_comment_lines) != '') {
            $comment_lines[$lineno] = $this_comment_lines;
          }
        // Save this line and start a new line.
        $this_all_lines = '';
        $this_php_lines = '';
Doug Green's avatar
Doug Green committed
        $this_allphp_lines = '';
        $this_html_lines = '';
        $this_quote_lines = '';
        $this_doublequote_lines = '';
        $this_comment_lines = '';
        $beginning_of_line = 1;
      if ($this_all_lines != '') {
        $beginning_of_line = 0;
      }
      $this_all_lines .= $char;
Doug Green's avatar
Doug Green committed
      if ($in_php || $in_allphp) {
        // When in a quoted string, look for the trailing quote
        // strip characters in the string, replacing with '' or "".
          }
          elseif ($char == $in_quote && !$in_backslash) {
          }
          elseif ($char == '<') {
            $in_quote_html = '>';
          }
          if ($in_quote) {
            $this_quote_lines .= $char;
            if ($in_quote == '"') {
              $this_doublequote_lines .= $char;
            }
            if ($in_quote_html) {
              $this_html_lines .= $char;
            }
Doug Green's avatar
Doug Green committed
          $this_allphp_lines .= $char;
          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) {
            $this_all_lines .= substr($content, $pos + 1, $in_heredoc_length - 1);
            $in_heredoc = 0;
            $pos += $in_heredoc_length;
          }
          elseif ($char == '<') {
            $in_heredoc_html = '>';
          }
          if ($in_heredoc && $in_heredoc_html) {
          if ($in_heredoc_html && $char == $in_heredoc_html) {
            $in_heredoc_html = '';
          }
          unset($char);
        }

        elseif ($char == '?' && $content[$pos + 1] == '>') {
          unset($char);
          $in_php = 0;
Doug Green's avatar
Doug Green committed
          $in_allphp = 0;
        // When in a comment look for the trailing comment.
          $this_comment_lines .= $char;
          if ($in_comment == '*' && $char == '*' && $content[$pos + 1] == '/') {
            $in_comment = 0;
            $this_all_lines .= '/';
            $this_comment_lines .= '/';