You are here

coder_review.module in Coder 7

Same filename and directory in other branches
  1. 7.2 coder_review/coder_review.module

File

coder_review/coder_review.module
View source
<?php

/**
 * @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
 * - 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
 */
if (module_exists('drush')) {
  include drupal_get_path('module', 'coder_review') . '/coder_review.drush.inc';
}
define('SEVERITY_MINOR', 1);
define('SEVERITY_NORMAL', 5);
define('SEVERITY_CRITICAL', 9);

/**
 * Implements hook_coder_review_ignore().
 */

/*
function coder_review_coder_review_ignore() {
 return array(
   'path' => drupal_get_path('module', 'coder_review'),
 );
}
*/

/**
 * Implements hook_flush_caches().
 */
function coder_review_flush_caches() {
  return array(
    'cache_coder',
  );
}

/**
 * Implements hook_help().
 */
function coder_review_help($page, $arg) {
  switch ($page) {
    case 'coder_review#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',
      ));
    case 'drush:coder_review':
      return t('coder_review [summary] [@reviews] [minor|major|critical] [active|core|all|default|<modules>] [no-<module>]', array(
        '@reviews' => implode('|', array_keys(_coder_review_reviews())),
      ));
  }
}

/**
 * Get all of the code review modules, including contributions.
 */
function _coder_review_reviews() {
  return module_invoke_all('reviews');
}

/**
 * Implements hook_reviews().
 */
function coder_review_reviews() {
  global $_coder_reviews;
  if (!isset($_coder_reviews)) {
    $_coder_reviews = array();
    $path = drupal_get_path('module', 'coder_review') . '/includes';
    $files = drupal_system_listing('/coder_review_.*\\.inc$/', $path, 'filepath', 0);
    foreach ($files as $file) {
      require_once DRUPAL_ROOT . '/' . $file->uri;
      $function = $file->name . '_reviews';
      if (function_exists($function)) {
        if ($review = call_user_func($function)) {
          $_coder_reviews = array_merge($_coder_reviews, $review);
        }
      }
    }
  }
  return $_coder_reviews;
}

/**
 * Implements hook_permission().
 */
function coder_review_permission() {
  return array(
    'view code review' => array(
      'title' => t('View code review'),
      'description' => t('Perform code reviews on modules and themes.'),
    ),
    'view code review all' => array(
      'title' => t('View all code reviews'),
      'description' => t('Perform code reviews on modules and themes, using the "all" modules feature.'),
    ),
  );
}

/**
 * Implements hook_menu().
 */
function coder_review_menu() {
  $items['admin/config/development/coder/review'] = array(
    'title' => 'Review',
    'page callback' => 'coder_review_page',
    'access arguments' => array(
      'view code review',
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -2,
  );
  $items['admin/config/development/coder/review/select'] = array(
    'title' => 'Select',
    'page callback' => 'coder_review_page',
    'access arguments' => array(
      'view code review',
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -2,
  );
  $items['admin/config/development/coder/review/default'] = array(
    'title' => 'Default',
    'page callback' => 'coder_review_page',
    'page arguments' => array(
      'default',
    ),
    'access arguments' => array(
      'view code review',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => -1,
  );
  $items['admin/config/development/coder/review/core'] = array(
    'title' => 'Core',
    'page callback' => 'coder_review_page',
    'page arguments' => array(
      'core',
    ),
    'access arguments' => array(
      'view code review',
    ),
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/config/development/coder/review/active'] = array(
    'title' => 'Active',
    'page callback' => 'coder_review_page',
    'page arguments' => array(
      'active',
    ),
    'access arguments' => array(
      'view code review',
    ),
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/config/development/coder/review/all'] = array(
    'title' => 'All',
    'page callback' => 'coder_review_page',
    'page arguments' => array(
      'all',
    ),
    'access arguments' => array(
      'view code review all',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 1,
  );
  $items['admin/config/development/coder/review/files'] = array(
    'title' => 'Files',
    'page callback' => 'coder_review_page',
    'page arguments' => array(
      'files',
    ),
    'access arguments' => array(
      'view code review',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 2,
  );
  $items['admin/config/development/coder/review/patches'] = array(
    'title' => 'Patches',
    'page callback' => 'coder_review_page',
    'page arguments' => array(
      'patches',
    ),
    'access arguments' => array(
      'view code review',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 3,
  );
  $items['admin/config/development/coder/review/settings'] = array(
    'title' => 'Settings',
    'description' => 'Select code review plugins and modules',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'coder_review_admin_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 3,
  );
  return $items;
}

/**
 * Implements hook_menu_alter().
 */
function coder_review_menu_alter(&$items) {
  $items['admin/config/development/coder']['page callback'] = 'coder_review_page';
}

/**
 * Implements hook_form_alter().
 *
 * Modify the module display view by adding a Coder Review link to every
 * module description.
 */
function coder_review_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'system_modules' && (!isset($form['#theme']) || $form['#theme'] != 'confirm_form')) {
    if (user_access('view code review')) {
      $form['#attached']['css'] = array(
        drupal_get_path('module', 'coder_review') . '/coder_review.css',
      );
      foreach ($form['modules'] as $package => $modules) {
        if (!preg_match('/^#/', $package)) {
          foreach ($modules as $name => $data) {
            if (!preg_match('/^#/', $name)) {
              $description = $data['description']['#markup'];
              $form['modules'][$package][$name]['description']['#markup'] = $description . ' (' . l(t('Code review'), "admin/config/development/coder/review/{$name}") . ')';
            }
          }
        }
      }
    }
  }
}

/**
 * Helper functions for settings form.
 */
function _coder_review_default_reviews() {
  return drupal_map_assoc(array(
    'style',
    'sql',
    'comment',
    'security',
    'i18n',
  ));
}

/**
 * Build settings form API array for coder_review.
 *
 * Generates a form with the default reviews and default modules/themes to
 * run Coder on.
 *
 * Actual forms may have additional sections added to them, this is simply a
 * base.
 *
 * @param $settings
 *   Settings array for coder_review in the format of _coder_review_get_default_settings().
 * @param $system
 *   Array of module and theme information, in form string theme/module
 *   name => boolean TRUE if checked by coder_review 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.
 */
function _coder_review_settings_form($settings, &$system, &$files) {

  // Add the javascript.
  $form['#attached']['js'] = array(
    drupal_get_path('module', 'coder_review') . '/coder_review.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.
  $reviews = _coder_review_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'];
  }

  // Sort the reviews by #title.
  asort($review_sort);
  foreach ($review_sort as $name => $review) {
    $review_sort[$name] = $review_options[$name];
  }

  // What reviews should be used?
  $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_sort,
    '#description' => t('apply the checked coding reviews'),
    '#default_value' => $settings['coder_reviews'],
  );

  // What severities should be used?
  $form['coder_reviews_group']['coder_severity'] = array(
    '#type' => 'radios',
    '#options' => array(
      SEVERITY_MINOR => 'minor (most)',
      SEVERITY_NORMAL => 'normal',
      SEVERITY_CRITICAL => 'critical (fewest)',
    ),
    '#description' => t('show warnings at or above the severity warning level'),
    '#default_value' => $settings['coder_severity'],
  );
  if (!empty($settings['coder_files'])) {
    $form['coder_what'] = array(
      '#type' => 'fieldset',
      '#title' => t('What to review'),
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
    );
    $form['coder_what']['coder_file_list'] = array(
      '#type' => 'textarea',
      '#title' => t('List of files'),
      '#description' => t('List each relative file path on a separate line.'),
      '#default_value' => isset($settings['coder_file_list']) ? $settings['coder_file_list'] : '',
    );
    $form['coder_files'] = array(
      '#type' => 'value',
      '#value' => 1,
    );
    if (!empty($settings['coder_file_list'])) {
      $lines = explode("\n", $settings['coder_file_list']);
      foreach ($lines as $line) {
        $line = trim($line);
        $system[$line] = $line;
        $files[$line] = $line;
      }
    }
  }
  elseif (!empty($settings['coder_patches'])) {

    // Display what to review options.
    $form['coder_what'] = array(
      '#type' => 'fieldset',
      '#title' => t('What to review'),
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
    );
    $form['coder_what']['coder_patch_help'] = array(
      '#markup' => '<p>' . t('All patches must be in unified format. It\'s also preferable for the patch to be created with the "-p" option, which shows the function closest to the code change. Without this, some function dependent tests may not be triggered. Coder offers no guarantee that the patch will apply cleanly or will function correctly.') . '</p>',
      '#weight' => -4,
    );
    $form['coder_what']['coder_patch_link'] = array(
      '#type' => 'textfield',
      '#title' => t('Link to patch'),
      '#default_value' => isset($settings['coder_patch_link']) ? $settings['coder_patch_link'] : '',
      '#weight' => -3,
    );
    $form['coder_what']['coder_patch_help'] = array(
      '#markup' => '<p>' . t('Or') . '</p>',
      '#weight' => -2,
    );
    $form['coder_what']['coder_patch_text'] = array(
      '#type' => 'textarea',
      '#title' => t('Patch text'),
      '#rows' => 20,
      '#weight' => -1,
      '#default_value' => isset($settings['coder_patch_text']) ? $settings['coder_patch_text'] : '',
      '#attributes' => array(
        'wrap' => 'OFF',
      ),
    );
    $form['coder_patches'] = array(
      '#type' => 'value',
      '#value' => 1,
    );
    $in_patch = 0;
    $patch = $link_contents = $textarea_contents = '';
    $patches = array();
    if (!empty($settings['coder_patch_link'])) {
      $link_contents = file_get_contents($settings['coder_patch_link']);
    }
    if (!empty($settings['coder_patch_text'])) {
      $textarea_contents = $settings['coder_patch_text'];
    }
    $patch_contents = $link_contents . "\n" . $textarea_contents;
    $lines = preg_split("/(\r\n|\n)/", $patch_contents);
    foreach ($lines as $line) {
      if ($line == '') {
        continue;
      }
      if (preg_match("/^\\+\\+\\+\\s+([\\w\\.\\-\\/]+)\\s*.*?\$/", $line, $matches)) {
        if (!empty($patch)) {
          $patches[$filename . ': ' . $patch_line_numbers] = $patch;
          $system[$filename . ': ' . $patch_line_numbers] = $filename;
          $patch = '';
        }
        $filename = $matches[1];
      }
      elseif (preg_match("/^(@@\\s+\\-\\d+,\\d+\\s+\\+\\d+,\\d+\\s+@@)\\s*((function|(protected|private|public)*\\s*class)\\s([\\w]+).*?)*\$/", $line, $matches)) {
        if (!empty($patch)) {
          $patches[$filename . ': ' . $patch_line_numbers] = $patch;
          $system[$filename . ': ' . $patch_line_numbers] = $filename;
          $patch = '';
        }
        if (isset($matches[3]) && isset($matches[4])) {
          if ($matches[3] == 'function') {
            $function_current = $matches[4];
            $patch = 'function ' . $function_current . "() {\n";
          }
          else {
            $class_current = $matches[4];
            $patch = $matches[2] . "\n";
          }
        }
        $patch_line_numbers = $matches[1];
      }
      elseif (preg_match("/^\\+(?!\\+)/", $line)) {
        $patch .= ltrim($line, '+') . "\n";
        $in_patch = 1;
      }
      elseif (preg_match("/^\\s/", $line)) {
        $patch .= drupal_substr($line, 1) . "\n";
        $in_patch = 1;
      }
      else {
        $in_patch = 0;
      }
    }
    if (!empty($patch)) {
      $patches[$filename . ': ' . $patch_line_numbers] = $patch;
      $system[$filename . ': ' . $patch_line_numbers] = $filename;
      $patch = '';
    }
    $files = $patches;
  }
  else {

    // 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';
    $result = db_query($sql);
    $system_modules = array();
    $system_themes = array();
    foreach ($result as $system) {
      $display_name = $system->name;
      if ($system->status) {
        $display_name .= ' (' . t('active') . ')';
        $system_active[$system->name] = $system->name;
      }
      if (_coder_review_is_drupal_core($system)) {
        $display_name .= ' (' . t('core') . ')';
        $system_core[$system->name] = $system->name;
      }
      if ($system->type == 'module') {
        $system_modules[$system->name] = $system->name;
      }
      else {
        $system_themes[$system->name] = $system->name;
      }
      $system_links[$system->name] = l($display_name, "admin/config/development/coder/review/{$system->name}");
      $files[$system->name] = $system->filename;
    }
    asort($system_links);

    // Display what to review options.
    $form['coder_what'] = array(
      '#type' => 'fieldset',
      '#title' => t('What to review'),
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
    );

    // NOTE: Should rename var.
    $form['coder_what']['coder_active_modules'] = array(
      '#type' => 'checkbox',
      '#default_value' => isset($settings['coder_active_modules']) ? $settings['coder_active_modules'] : 0,
      '#title' => t('active modules and themes'),
    );
    $form['coder_what']['coder_core'] = array(
      '#type' => 'checkbox',
      '#default_value' => isset($settings['coder_core']) ? $settings['coder_core'] : 0,
      '#title' => t('core files (php, modules, and includes)'),
    );
    $ext = variable_get('coder_review_php_ext', array(
      'inc',
      'php',
      'install',
      'test',
    ));
    $form['coder_what']['coder_includes'] = array(
      '#type' => 'checkbox',
      '#default_value' => isset($settings['coder_includes']) ? $settings['coder_includes'] : 0,
      '#title' => t('include files (@ext)', array(
        '@ext' => implode(' | ', $ext),
      )),
    );
    $form['coder_what']['coder_includes_exclusion_fieldset'] = array(
      '#type' => 'fieldset',
      '#title' => t('Include file exclusions'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
    );
    $form['coder_what']['coder_includes_exclusion_fieldset']['coder_includes_exclusions'] = array(
      '#rows' => 3,
      '#type' => 'textarea',
      '#default_value' => isset($settings['coder_includes_exclusions']) ? $settings['coder_includes_exclusions'] : '',
      '#description' => t('List file names or paths, one per line, which should be excluded (only valid if "include files" is checked above). For example, modules/system/*.php will exclude all php files in the modules/system directory.'),
    );

    // Display the modules in a fieldset.
    $form['coder_what']['coder_modules'] = array(
      '#type' => 'fieldset',
      '#title' => t('Select specific modules'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      'checkboxes' => array(
        '#theme' => 'coder_review_table_cols',
        '#cols' => 2,
      ),
    );
    if (isset($settings['coder_all'])) {
      $modules = $system_modules;
    }
    elseif (isset($settings['coder_active_modules']) && $settings['coder_active_modules']) {
      if (isset($settings['coder_core']) && $settings['coder_core']) {
        $modules = array_intersect($system_active, $system_core);
        $modules = array_intersect($modules, $system_modules);
      }
      else {
        $modules = array_intersect($system_active, $system_modules);
      }
    }
    elseif (isset($settings['coder_core']) && $settings['coder_core']) {
      $modules = array_intersect($system_core, $system_modules);
    }
    elseif (isset($settings['coder_active_modules']) && $settings['coder_active_modules']) {
      $modules = array_intersect($system_active, $system_modules);
    }
    elseif (isset($settings['coder_contrib']) && $settings['coder_contrib']) {
      $modules = array_diff($system_modules, array_intersect($system_core, $system_modules));
    }
    else {
      $modules = isset($settings['coder_modules']) && is_array($settings['coder_modules']) ? $settings['coder_modules'] : array();
    }

    // Display the themes in a fieldset.
    $form['coder_what']['coder_themes'] = array(
      '#type' => 'fieldset',
      '#title' => t('Select specific themes'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      'checkboxes' => array(
        '#theme' => 'coder_review_table_cols',
      ),
    );
    if (isset($settings['coder_all'])) {
      $themes = $system_themes;
    }
    elseif (isset($settings['coder_active_modules']) && $settings['coder_active_modules']) {
      if (isset($settings['coder_core']) && $settings['coder_core']) {
        $themes = array_intersect($system_active, $system_core);
        $themes = array_intersect($themes, $system_themes);
      }
      else {
        $themes = array_intersect($system_active, $system_themes);
      }
    }
    elseif (isset($settings['coder_core']) && $settings['coder_core']) {
      $themes = array_intersect($system_core, $system_themes);
    }
    elseif (isset($settings['coder_active_modules']) && $settings['coder_active_modules']) {
      $themes = array_intersect($system_active, $system_themes);
    }
    elseif (isset($settings['coder_contrib']) && $settings['coder_contrib']) {
      $themes = array_diff($system_themes, array_intersect($system_core, $system_themes));
    }
    else {
      $themes = isset($settings['coder_themes']) && is_array($settings['coder_themes']) ? $settings['coder_themes'] : array();
    }
    foreach ($system_links as $name => $link) {
      $classes = array();
      if (in_array($name, $system_active)) {
        $classes[] = 'coder-active';
      }
      if (in_array($name, $system_core)) {
        $classes[] = 'coder-core';
      }
      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(
        '#type' => 'checkbox',
        '#title' => $link,
        '#default_value' => $default_value,
        '#attributes' => array(
          'class' => $classes,
        ),
      );
    }
    $system = array_merge($modules, $themes);
  }
  return $form;
}

/**
 * Implement theme_cols to theme the radiobuttons and checkboxes form
 * elements in a table column.
 */
function theme_coder_review_table_cols($variables) {
  if (!isset($variables['form']) || !is_array($variables['form'])) {
    return '';
  }
  $form = $variables['form'];
  $total = 0;
  $cols = isset($form['#cols']) ? $form['#cols'] : 3;
  foreach ($form as $element_id => $element) {
    if ($element_id[0] != '#') {
      $total++;
    }
  }
  $total = (int) ($total % $cols ? ($total + $cols - 1) / $cols : $total / $cols);
  $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);
    }
  }
  $output = theme('table', array(
    'rows' => $rows,
    'attributes' => array(
      'id' => 'filter-order',
    ),
  ));
  return $output;
}

/**
 * Configuration page.
 */
function coder_review_admin_settings($form, &$form_state) {
  $settings = _coder_review_get_default_settings();
  $form = _coder_review_settings_form($settings, $system, $files);
  $form['help'] = array(
    '#type' => 'markup',
    '#markup' => t('After setting these defaults, use <a href="@url">coder review</a> to perform code reviews.', array(
      '@url' => url('admin/config/development/coder/review'),
    )),
    '#weight' => -1,
  );
  $form['#submit'][] = 'coder_review_settings_form_submit';
  return system_settings_form($form);
}

/**
 * Callback function for settings page.
 */
function coder_review_settings_form_submit($form, &$form_state) {
  $form_state['storage'] = $form_state['values'];
  variable_set('coder_modules', _coder_review_settings_array($form_state, 'module'));
  variable_set('coder_themes', _coder_review_settings_array($form_state, 'theme'));
}

/**
 * Generate settings array for either modules or themes.
 *
 * @param $form_state
 *   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
 */
function _coder_review_settings_array(&$form_state, $type) {
  $typekey = "coder_{$type}s-";
  $typelen = drupal_strlen($typekey);
  $systems = array();
  foreach ($form_state['storage'] as $key => $value) {
    if (drupal_substr($key, 0, $typelen) == $typekey) {
      if ($value == 1) {
        $system = drupal_substr($key, $typelen);
        $systems[$system] = 1;
      }
      unset($form_state['storage'][$key]);
      unset($form_state['values'][$key]);
    }
  }
  return $systems;
}

/**
 * Code review page.
 */
function coder_review_page($type = '') {
  $form = drupal_get_form('coder_review_page_form', $type);
  $output = '<div class="coder-disclaimer">' . coder_review_help('coder#disclaimer', array()) . '</div>';
  $output .= drupal_render($form);
  return $output;
}

/**
 * Returns a active settings array for coder_review.
 *
 * 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_review_get_default_settings($arg = 'default') {
  $settings['coder_reviews'] = variable_get('coder_reviews', _coder_review_default_reviews());
  $settings['coder_severity'] = variable_get('coder_severity', SEVERITY_NORMAL);

  // Determine any options based on the passed in URL.
  switch ($arg) {
    case 'settings':
      $settings['coder_includes'] = 1;
      break;
    case 'active':
      $settings['coder_active_modules'] = 1;
      break;
    case 'core':
      $settings['coder_core'] = 1;
      $settings['coder_includes'] = 1;
      break;
    case 'all':
      $settings['coder_core'] = 1;
      $settings['coder_includes'] = 1;
      $settings['coder_all'] = 1;
      break;
    case 'contrib':
      $settings['coder_includes'] = 1;
      $settings['coder_contrib'] = 1;
      break;
    case 'files':
      $settings['coder_files'] = 1;
      break;
    case 'patches':
      $settings['coder_patches'] = 1;
      break;
    case 'default':
      $settings['coder_active_modules'] = variable_get('coder_active_modules', 1);
      $settings['coder_core'] = variable_get('coder_core', 0);
      $settings['coder_includes'] = variable_get('coder_includes', 0);
      $settings['coder_includes_exclusions'] = variable_get('coder_includes_exclusions', '');
      $settings['coder_modules'] = variable_get('coder_modules', array());
      $settings['coder_themes'] = variable_get('coder_themes', array());
      break;
    default:
      $settings['coder_includes'] = 1;
      $settings['coder_includes_exclusions'] = variable_get('coder_includes_exclusions', '');

      // TODO: Does this need to go into coder_review_themes sometimes?
      $settings['coder_modules'] = array(
        $arg => $arg,
      );
      break;
  }
  return $settings;
}

/**
 * Implements hook_submit().
 */
function coder_review_page_form_submit($form, &$form_state) {

  // In D7, setting values in this variable does not trigger a form rebuild.
  $form_state['storage'] = $form_state['values'];

  // Rebuild form with user selections.
  $form_state['rebuild'] = TRUE;
}

/**
 * Return the extensions used by the reviews, that aren't part of the default extensions.
 */
function _coder_review_get_reviews_extensions($defaults, $reviews) {
  $extensions = array();
  foreach ($reviews as $review) {
    foreach ($review['#rules'] as $rule) {
      if (isset($rule['#filename'])) {
        foreach ($rule['#filename'] as $ext) {
          if (!in_array($ext, $defaults) && !in_array($ext, $extensions)) {
            $extensions[] = $ext;
          }
        }
      }
    }
  }
  return $extensions;
}

/**
 * Implements hook_form().
 *
 * Implements coder_review's main form, in which a user can select reviews and
 * modules/themes to run them on.
 */
function coder_review_page_form($form, &$form_state, $arg = '') {
  if (isset($form_state['storage'])) {
    $settings = $form_state['storage'];
    $settings['coder_modules'] = _coder_review_settings_array($form_state, 'module');
    $settings['coder_themes'] = _coder_review_settings_array($form_state, 'theme');
    drupal_set_title(t('Code review (submitted options)'), PASS_THROUGH);
  }
  else {
    $settings = _coder_review_get_default_settings($arg);
    if ($arg) {
      drupal_set_title(t('Code review (@options)', array(
        '@options' => isset($arg) ? $arg : 'default options',
      )), PASS_THROUGH);
    }
  }

  // Get this once: list of the reviews to perform.
  $reviews = array();
  $avail_reviews = _coder_review_reviews();
  $selected_reviews = $settings['coder_reviews'];
  foreach ($selected_reviews as $name => $checked) {
    if ($checked) {
      $reviews[$name] = $avail_reviews[$name];
    }
  }
  if ($coder_form = _coder_review_settings_form($settings, $system, $files)) {

    // Get the includes_exclusions settings once, it is used multiple times.
    $coder_includes_exclusions = isset($settings['coder_includes_exclusions']) ? $settings['coder_includes_exclusions'] : '';

    // Get the list of lines to ignore.
    $ignores = coder_review_get_ignores();

    // Code review non-module core files.
    $module_weight = 0;
    if (isset($settings['coder_core']) && $settings['coder_core']) {
      $php_extensions = variable_get('coder_review_php_ext', array(
        'inc',
        'php',
        'install',
        'test',
      ));
      $coder_args = array(
        '#reviews' => $reviews,
        '#severity' => $settings['coder_severity'],
        '#php_extensions' => $php_extensions,
        '#include_extensions' => array(),
        //'#filename' => $filename,
        '#cache' => TRUE,
      );
      drupal_alter('coder_review_args', $coder_args);
      $form['core_php'] = array(
        '#type' => 'fieldset',
        '#title' => 'core (php)',
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
        '#weight' => ++$module_weight,
      );
      $phpfiles = file_scan_directory('.', '/.*\\.php/', array(
        'recurse' => FALSE,
        'key' => 'name',
      ));
      _coder_review_page_form_includes($form, $coder_args, 'core_php', $phpfiles, 2, 0, $coder_includes_exclusions, $ignores);
      $form['core_includes'] = array(
        '#type' => 'fieldset',
        '#title' => 'core (includes)',
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
        '#weight' => ++$module_weight,
      );
      $includefiles = drupal_system_listing('/.*\\.inc$/', 'includes', 'filepath', 0);
      _coder_review_page_form_includes($form, $coder_args, 'core_includes', $includefiles, 0, $coder_includes_exclusions, $ignores);
    }

    // Loop through the selected modules and themes.
    if (isset($system)) {

      // Determine which file extensions to include, based on the rules.
      $php_extensions = variable_get('coder_review_php_ext', array(
        'inc',
        'php',
        'install',
        'test',
      ));
      $include_extensions = _coder_review_get_reviews_extensions($php_extensions, $reviews);
      $includes = array_merge($php_extensions, $include_extensions);

      // Used to avoid duplicate includes.
      $dups = array();
      $stats = array();
      $patch = '';
      foreach ($system as $name => $checked) {
        if ($checked) {

          // Process this one file.
          $filename = isset($files[$name]) ? $files[$name] : FALSE;
          if (!$filename) {
            drupal_set_message(t('Code review file for %module not found', array(
              '%module' => $name,
            )), 'error');
            continue;
          }
          if (!empty($settings['coder_patches'])) {
            $patch = $filename;
            $filename = $name;
          }
          $coder_args = array(
            '#reviews' => $reviews,
            '#severity' => $settings['coder_severity'],
            '#filename' => $filename,
            '#patch' => $patch,
            '#php_extensions' => $php_extensions,
            '#include_extensions' => $include_extensions,
            '#ignore_lines' => isset($ignores[$filename]) && is_array($ignores[$filename]) ? $ignores[$filename] : array(),
            '#cache' => TRUE,
          );
          drupal_alter('coder_review_args', $coder_args);
          $results = do_coder_reviews($coder_args);
          $stats[$filename] = $results['#stats'];
          unset($results['#stats']);
          module_invoke_all('coder_review_results_view', $results);

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

          // Process the same directory include files.
          if (!empty($settings['coder_includes'])) {

            // NOTE: Convert to the realpath here so drupal_system_listing
            // doesn't return additional paths (i.e., try "module").
            if ($path = str_replace('\\', '/', dirname(realpath($filename)))) {
              if (!isset($dups[$path])) {
                if (drupal_substr($filename, -7) == '.module') {
                  $coder_args['#php_minor'] = 1;
                }
                $dups[$path] = 1;
                $regex = '/\\.(' . implode('|', $includes) . ')$/';
                $includefiles = drupal_system_listing($regex, dirname($filename), 'filepath', 0);
                $offset = strpos($filename, dirname($filename));
                $stats[$filename]['#includes'] = _coder_review_page_form_includes($form, $coder_args, $name, $includefiles, $offset, $coder_includes_exclusions, $ignores);
              }
            }
          }
        }
      }
      if (count($stats)) {
        $summary = array(
          'files' => 0,
          'minor' => 0,
          'normal' => 0,
          'critical' => 0,
          'ignored' => 0,
        );
        foreach ($stats as $stat) {
          $summary['minor'] += $stat['minor'];
          $summary['normal'] += $stat['normal'];
          $summary['critical'] += $stat['critical'];
          $summary['ignored'] += $stat['ignored'];
          if (isset($stat['#includes'])) {
            foreach ($stat['#includes'] as $includestat) {
              $summary['files']++;
              $summary['minor'] += $includestat['minor'];
              $summary['normal'] += $includestat['normal'];
              $summary['critical'] += $includestat['critical'];
              $summary['ignored'] += $includestat['ignored'];
            }
          }
          $summary['files']++;
        }
        $display = array();
        if (empty($settings['coder_patches'])) {
          $display[] = t('Coder found @count projects', array(
            '@count' => count($stats),
          ));
          $display[] = t('@count files', array(
            '@count' => $summary['files'],
          ));
        }
        else {
          $display[] = t('Coder found @count patch hunks', array(
            '@count' => count($stats),
          ));
        }
        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,
            ));
          }
        }
        $display[] = t('@count warnings were flagged to be ignored', array(
          '@count' => $summary['ignored'],
        ));
        drupal_set_message(implode(', ', $display));
        if (function_exists('_coder_review_drush_is_option') && _coder_review_drush_is_option('drush')) {
          print coder_review_print_drush_messages();
        }
      }
    }

    // Prepend the settings form.
    $form['settings'] = array(
      '#type' => 'fieldset',
      '#title' => t('Selection form'),
      '#collapsible' => TRUE,
      '#collapsed' => !empty($form),
      '#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/config/development/coder/review/settings'),
        '@default' => url('admin/config/development/coder/review/default'),
      ));
    }
    $form['settings'][] = $coder_form;
    $form['settings']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Run reviews'),
    );
    $form['#attached']['css'] = array(
      drupal_get_path('module', 'coder_review') . '/coder_review.css',
    );
  }
  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.
 * @param $exclusions
 *   Exclusions that should be ignored in $files.
 * @param $ignores
 *   Array of lines per file to be skipped for defined reviews.
 * @return
 *   Statistics array in form: string filename => array value of
 *   '#stats' from do_coder_reviews().
 */
function _coder_review_page_form_includes(&$form, $coder_args, $name, $files, $offset, $exclusions, $ignores) {
  $stats = array();
  $coder_args['#name'] = $name;
  $weight = 0;
  $exclusions = str_replace(array(
    "\r\n",
    "\r",
    "\n",
    '/',
    '*',
    '.',
  ), array(
    '|',
    '|',
    '|',
    '\\/',
    '.*',
    '\\.',
  ), $exclusions);
  foreach ($files as $file) {
    if (!empty($exclusions) && preg_match("/{$exclusions}/", $file->uri)) {
      continue;

      // Don't review this file.
    }
    $filename = drupal_substr($file->uri, $offset);
    $php_extensions = variable_get('coder_review_php_ext', array(
      'inc',
      'php',
      'install',
      'test',
    ));
    $coder_args['#filename'] = $filename;
    $coder_args['#php_extensions'] = $php_extensions;
    $coder_args['#ignore_lines'] = isset($ignores[$filename]) && is_array($ignores[$filename]) ? $ignores[$filename] : array();
    $results = do_coder_reviews($coder_args);
    $stats[$filename] = $results['#stats'];
    unset($results['#stats']);
    module_invoke_all('coder_review_results_view', $results);

    // Output the results in a collapsible fieldset.
    $form[$name][$filename] = array(
      '#type' => 'fieldset',
      '#title' => $filename,
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#weight' => ++$weight,
    );
    if (empty($results)) {
      $results[] = t('No Problems Found');
    }
    else {
      $form[$name][$filename]['#collapsed'] = FALSE;
      $form[$name]['#collapsed'] = FALSE;
    }
    if (function_exists('_coder_review_drush_is_option') && _coder_review_drush_is_option('drush')) {
      theme('coder_review', array(
        'name' => $name,
        'filename' => $filename,
        'results' => $results,
      ));
    }
    else {
      $form[$name][$filename]['output'] = array(
        '#theme' => 'coder_review',
        '#name' => $name,
        '#filename' => $filename,
        '#results' => $results,
      );
    }
  }
  return $stats;
}
function _coder_review_form_output($name, $filename, $results) {
  $output = array(
    '#theme' => 'coder_review',
    '#name' => $name,
    '#filename' => $filename,
    '#results' => $results,
  );

  // When using Drush, render this element so that the theme output gets recorded.
  if (function_exists('_coder_review_drush_is_option') && _coder_review_drush_is_option('drush')) {
    render($output);
  }
  return $output;
}

/**
 * Return last modification timestamp of coder_review and all of its dependencies.
 */
function _coder_review_modified() {
  static $_coder_mtime;
  if (!isset($_coder_mtime)) {
    $path = drupal_get_path('module', 'coder_review');
    $includefiles = drupal_system_listing('/.*\\.(inc|module|coder_review_ignores.txt)$/', $path . '/includes', 'filepath', 0);
    $_coder_mtime = filemtime(realpath($path . '/coder_review.module'));
    foreach ($includefiles as $file) {
      $mtime = filemtime(realpath($path . '/includes/' . $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_review_reviews();
 *     - '#severity' => integer magic number, see constants SEVERITY_*;
 *     - '#filename' => string filename to check,
 *     - '#patch' => string patch lines 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) {

  // Load the cached results if they exist, but not for patches.
  if (empty($coder_args['#patch']) && empty($coder_args['#test']) && $coder_args['#cache']) {
    $cache_key = 'coder:' . md5(implode(':', array_keys($coder_args['#reviews']))) . $coder_args['#severity'] . ':' . $coder_args['#filename'];
    if (function_exists('_coder_review_drush_is_option') && _coder_review_drush_is_option('drush')) {
      if (_coder_review_drush_is_option('checkstyle')) {
        $cache_key .= ':drushcheckstyle';
      }
      elseif (_coder_review_drush_is_option('xml')) {
        $cache_key .= ':drushxml';
      }
    }
    if (file_exists($filepath = realpath($coder_args['#filename']))) {
      $cache_mtime = filemtime($filepath);
      if ($cache_results = cache_get($cache_key, 'cache_coder')) {
        if ($cache_results->data['mtime'] == $cache_mtime && _coder_review_modified() < $cache_results->created) {
          return $cache_results->data['results'];
        }
      }
    }
  }
  $results = array(
    '#stats' => array(
      'minor' => 0,
      'normal' => 0,
      'critical' => 0,
      'ignored' => 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;
    }
  }

  // Read the file.
  if (_coder_review_read_and_parse_file($coder_args)) {

    // Do all of the code reviews.
    $errors = array();
    foreach ($coder_args['#reviews'] as $review_name => $review) {
      $review['#review_name'] = $review_name;
      if ($result = do_coder_review($coder_args, $review)) {

        // ignored isn't a severity level, but is a stat we need to track.
        foreach (array(
          'critical',
          'normal',
          'minor',
          'ignored',
        ) as $severity_level) {
          if (isset($result['#stats'][$severity_level])) {
            $results['#stats'][$severity_level] += $result['#stats'][$severity_level];
          }
        }
        $errors += $result;
      }
    }

    // Theme the error messages.
    foreach ($errors as $key => $error) {
      if (is_numeric($key)) {
        $error['warning'] = _coder_review_warning($error['rule']);
        $results[$key] = theme('coder_review_warning_msg', array(
          'error' => $error,
        ));
      }
    }

    // Sort the results.
    ksort($results, SORT_NUMERIC);
  }
  else {
    $results[] = theme('coder_review_warning', array(
      'warning' => t('Could not read the file'),
      'severity_name' => 'critical',
    ));
  }

  // Save the results in the cache if we're not reviewing a patch.
  if (empty($coder_args['#patch']) && empty($coder_args['#test']) && $coder_args['#cache'] && isset($cache_mtime)) {
    $cache_results = array(
      'mtime' => $cache_mtime,
      'results' => $results,
    );
    cache_set($cache_key, $cache_results, 'cache_coder');
  }
  return $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_array_lines', '#php_array_lines', '#allphp_array_lines',
 *     '#html_array_lines', '#quote_array_lines', '#doublequote_array_lines',
 *     '#comment_array_lines', and #all_lines.
 *   The _array_ variants are multidimensional arrays, the first index for
 *   the line number, and the second index for each occurance within the line.
 *   #all_lines is a simple array, with each line from the file as an index.
 * @return
 *   Integer 1 if success.
 */
function _coder_review_read_and_parse_file(&$coder_args) {
  $is_php_file = 1;
  $regex = '/(' . implode('|', array_merge(array(
    'module',
  ), $coder_args['#php_extensions'])) . ')$/';
  if (!preg_match($regex, $coder_args['#filename'])) {
    $is_php_file = 0;

    /*
      // If this isn't a php file, then don't try to parse it.
      if ((($filepath = realpath($coder_args['#filename'])) && file_exists($filepath))) {
        $full_lines = file($filepath);
        if ($full_lines[0] != '<?php') {
          foreach ($full_lines as $lineno => $line) {
            if (($line = trim($line, "\r\n")) != '') {
              $all_lines[$lineno] = $line;
            }
          }
          $coder_args['#all_lines'] = $all_lines;
          return 1;
        }
      }
    */
  }
  $filepath = realpath($coder_args['#filename']);

  // Get the path to the module file.
  if (!empty($coder_args['#patch']) || !empty($coder_args['#test']) || ($filepath = realpath($coder_args['#filename'])) && file_exists($filepath)) {
    $in_php = $is_php_file ? 0 : 1;
    $in_allphp = $is_php_file ? 0 : 1;
    $in_comment = 0;
    if (!empty($coder_args['#patch'])) {
      $content = $coder_args['#patch'];
      if (preg_match('/^\\s*\\*/', $content)) {
        $in_comment = '*';
      }
      else {
        $content = preg_replace('/^(function\\s.*?(\\r\\n|\\n)+)(\\s*\\*)/', '${1}/*', $content);
        $in_php = 1;
        $in_allphp = 1;
      }
    }
    elseif (!empty($coder_args['#test'])) {
      $content = $coder_args['#test'];
      $in_php = 1;
      $in_allphp = 1;
    }
    else {
      $content = file_get_contents($filepath) . "\n";
    }
    $content_length = drupal_strlen($content);
    $in_comment = 0;
    $beginning_of_line = 0;
    $in_quote_html = 0;
    $in_backslash = 0;
    $in_quote = 0;
    $in_heredoc = 0;
    $in_heredoc_html = '';
    $heredoc = '';
    $all_lines = array();
    $full_lines = array();
    $php_lines = array();
    $allphp_lines = array();
    $html_lines = array();
    $quote_lines = array();
    $doublequote_lines = array();
    $comment_lines = array();
    $this_all_lines = '';
    $this_php_lines = '';
    $this_allphp_lines = '';
    $this_html_lines = '';
    $this_quote_lines = array(
      '',
    );
    $this_quote_index = -1;
    $this_quote_sep = FALSE;
    $this_doublequote_lines = array(
      '',
    );
    $this_doublequote_index = -1;
    $this_comment_lines = '';

    // Parse the file:
    // - Strip comments,
    // - Strip quote content,
    // - Strip stuff not in php,
    // - Break into lines.
    $lineno = 1;
    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.
          $in_comment = 0;
        }

        // Assume that html inside quotes doesn't span newlines.
        $in_quote_html = 0;

        // Remove coder's simpletests assertions as they validly contain bad
        // code, for testing the review rules.
        if (preg_match('/assertCoderReview(Fail|Pass)/', $this_all_lines)) {
          $lineno++;
          $this_all_lines = '';
          $this_php_lines = '';
          $this_allphp_lines = '';
          $this_html_lines = '';
          $this_comment_lines = '';
          $this_quote_lines = array(
            '',
          );
          continue;
        }

        // Remove blank lines now, so we avoid processing them over-and-over.
        if ($this_all_lines != '') {
          if (trim($this_all_lines, "\r\n") != '') {
            $all_lines[$lineno] = array(
              $this_all_lines,
            );
            $full_lines[$lineno] = $this_all_lines;
          }
          if (trim($this_php_lines, "\r\n") != '') {
            $php_lines[$lineno] = array(
              $this_php_lines,
            );
          }
          if (trim($this_allphp_lines, "\r\n") != '') {
            $allphp_lines[$lineno] = array(
              $this_allphp_lines,
            );
          }
          if (trim($this_html_lines, "\r\n") != '') {
            $html_lines[$lineno] = array(
              $this_html_lines,
            );
          }
          $quotes = array();
          foreach ($this_quote_lines as $quote_line) {
            if (trim($quote_line, "\r\n") != '') {
              $quotes[] = $quote_line;
            }
          }
          if (count($quotes)) {
            $quote_lines[$lineno] = $quotes;
          }
          $quotes = array();
          foreach ($this_doublequote_lines as $quote_line) {
            if (trim($quote_line, "\r\n") != '') {
              $quotes[] = $quote_line;
            }
          }
          if (count($quotes)) {
            $doublequote_lines[$lineno] = $quotes;
          }
          if (trim($this_comment_lines, "\r\n") != '') {
            $comment_lines[$lineno] = array(
              $this_comment_lines,
            );
          }
        }

        // Save this line and start a new line.
        $lineno++;
        $this_all_lines = '';
        $this_php_lines = '';
        $this_allphp_lines = '';
        $this_html_lines = '';
        $this_quote_lines = array(
          '',
        );
        $this_doublequote_lines = array(
          '',
        );
        $this_quote_index = -1;
        $this_quote_sep = FALSE;
        $this_doublequote_index = -1;
        $this_comment_lines = '';
        $beginning_of_line = 1;
        continue;
      }
      if ($this_all_lines != '') {
        $beginning_of_line = 0;
      }
      $this_all_lines .= $char;
      if ($in_php || $in_allphp) {

        // When in a quoted string, look for the trailing quote
        // strip characters in the string, replacing with '' or "".
        if ($in_quote) {
          if ($in_backslash) {
            $in_backslash = 0;
          }
          elseif ($char == '\\') {
            $in_backslash = 1;
          }
          elseif ($char == $in_quote && !$in_backslash) {
            $in_quote = 0;
          }
          elseif ($char == '<') {
            $in_quote_html = '>';
          }
          if ($in_quote) {
            if ($this_quote_index == -1) {
              $this_quote_index = 0;
            }
            $this_quote_lines[$this_quote_index] .= $char;
            if ($in_quote == '"') {
              if ($this_doublequote_index == -1) {
                $this_doublequote_index = 0;
              }
              $this_doublequote_lines[$this_doublequote_index] .= $char;
            }
            if ($in_quote_html) {
              $this_html_lines .= $char;
            }
          }
          if ($char == $in_quote_html) {
            $in_quote_html = 0;
          }
          $this_allphp_lines .= $char;
          unset($char);

          // NOTE: Trailing char output with starting one.
        }
        elseif ($in_heredoc) {
          if ($beginning_of_line && $char == $in_heredoc[0] && drupal_substr($content, $pos, $in_heredoc_length) == $in_heredoc) {
            $this_all_lines .= drupal_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) {
            $this_html_lines .= $char;
          }
          if ($in_heredoc_html && $char == $in_heredoc_html) {
            $in_heredoc_html = '';
          }
          unset($char);
        }
        elseif ($char == '?' && $content[$pos + 1] == '>' && $in_comment !== '*') {
          unset($char);
          $in_php = 0;
          $in_allphp = 0;
          $this_all_lines .= '>';
          $pos++;
        }
        elseif ($in_comment) {
          $this_comment_lines .= $char;
          if ($in_comment == '*' && $char == '*' && $content[$pos + 1] == '/') {
            $in_comment = 0;
            $this_all_lines .= '/';
            $this_comment_lines .= '/';
            $pos++;
          }
          unset($char);

          // Don't add comments to php output.
        }
        else {
          switch ($char) {
            case ',':
            case ')':
            case '(':
            case '>':

            // For 'foo' => 'bar' type syntax.
            case ':':

              // Look for separators which force a new quote string.
              if ($this_quote_index < 0 || !empty($this_quote_lines[$this_quote_index])) {
                $this_quote_sep = TRUE;
              }
              break;
            case '\'':
            case '"':
              if ($content[$pos - 1] != '\\') {
                $this_php_lines .= $char;
                $in_quote = $char;
                if ($this_quote_sep) {
                  $this_quote_lines[++$this_quote_index] = '';
                  if ($char == '"') {
                    $this_doublequote_lines[++$this_doublequote_index] = '';
                  }
                }
                $this_quote_sep = FALSE;
              }
              break;
            case '/':
              $next_char = $content[$pos + 1];
              if ($next_char == '/' || $next_char == '*') {
                unset($char);
                $in_comment = $next_char;
                $this_all_lines .= $next_char;
                $this_comment_lines .= '/' . $next_char;
                $pos++;
              }
              break;
            case '<':
              if ($content[$pos + 1] == '<' && $content[$pos + 2] == '<') {
                unset($char);
                $this_all_lines .= '<<';

                // 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;
                  }
                  $this_all_lines .= $char;
                  $heredoc .= $char;
                }
                $heredoc = '';

                // Replace heredoc's with an empty string.
                $this_php_lines .= '\'\'';
                $this_allphp_lines .= '\'\'';
                unset($char);
              }
              break;
          }
        }
        if (isset($char)) {
          $this_php_lines .= $char;
          $this_allphp_lines .= $char;
        }
      }
      else {
        switch ($char) {
          case '<':
            if ($content[$pos + 1] == '?') {
              if ($content[$pos + 2] == ' ') {
                $in_php = 1;
                $in_allphp = 1;
                $this_all_lines .= '? ';
                $pos += 2;
              }
              elseif (drupal_substr($content, $pos + 2, 3) == 'php') {
                $in_php = 1;
                $in_allphp = 1;
                $this_all_lines .= '?php';
                $pos += 4;
              }
              break;
            }

          // FALLTHROUGH
          default:
            $this_html_lines .= $char;
            break;
        }
      }
    }
    if (trim($this_all_lines) != '') {
      $all_lines[$lineno] = array(
        $this_all_lines,
      );
      $full_lines[$lineno] = $this_all_lines;
    }
    if (trim($this_php_lines) != '') {
      $php_lines[$lineno] = array(
        $this_php_lines,
      );
    }
    if (trim($this_allphp_lines) != '') {
      $allphp_lines[$lineno] = array(
        $this_allphp_lines,
      );
    }
    if (trim($this_html_lines) != '') {
      $html_lines[$lineno] = array(
        $this_html_lines,
      );
    }
    $quotes = array();
    foreach ($this_quote_lines as $quote_line) {
      if (trim($quote_line, "\r\n") != '') {
        $quotes[] = $quote_line;
      }
    }
    if (count($quotes)) {
      $quote_lines[$lineno] = $quotes;
    }
    $quotes = array();
    foreach ($this_doublequote_lines as $quote_line) {
      if (trim($quote_line, "\r\n") != '') {
        $quotes[] = $quote_line;
      }
    }
    if (count($quotes)) {
      $doublequote_lines[$lineno] = $quotes;
    }
    if (trim($this_comment_lines) != '') {
      $comment_lines[$lineno] = array(
        $this_comment_lines,
      );
    }

    // Add the files lines to the arguments.
    $coder_args['#all_array_lines'] = $all_lines;
    $coder_args['#php_array_lines'] = $php_lines;
    $coder_args['#allphp_array_lines'] = $allphp_lines;
    $coder_args['#html_array_lines'] = $html_lines;
    $coder_args['#quote_array_lines'] = $quote_lines;
    $coder_args['#doublequote_array_lines'] = $doublequote_lines;
    $coder_args['#comment_array_lines'] = $comment_lines;
    $coder_args['#all_lines'] = $full_lines;
    return 1;
  }
}

/**
 * Return the integer severity magic number for a string severity.
 *
 * @param $severity_name
 *   String severity name 'minor', 'normal', or 'critical'.
 * @param $default_value
 *   Integer magic number to use if severity string is not recognized.
 * @return
 *   Integer magic number, see SEVERITY_* constants.
 */
function _coder_review_severity($severity_name, $default_value = SEVERITY_NORMAL) {

  // NOTE: Implemented this way in hopes that it is faster than a php switch.
  if (!isset($severity_names)) {
    $severity_names = array(
      'minor' => SEVERITY_MINOR,
      'normal' => SEVERITY_NORMAL,
      'critical' => SEVERITY_CRITICAL,
    );
  }
  if (isset($severity_names[$severity_name])) {
    return $severity_names[$severity_name];
  }
  return $default_value;
}

/**
 * Return string severity for a given error.
 *
 * @param $coder_args
 *   Coder settings array, see do_coder_reviews().
 * @param $review
 *   Review array, see hook_reviews(), contains rule arrays.
 * @param $rule
 *   Rule array that was triggered, see individual entries from hook_reviews().
 * @return
 *   String severity of error.
 */
function _coder_review_severity_name($coder_args, $review, $rule) {

  // NOTE: Warnings in php includes are suspicious because
  // php includes are frequently 3rd party products.
  if (isset($coder_args['#php_minor']) && drupal_substr($coder_args['#filename'], -4) == '.php') {
    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';
}

/**
 * Perform code review for a review array.
 *
 * @param $coder_args
 *   Array coder_review settings, must have been prepared with _coder_review_read_and_parse_file(),
 *   see do_coder_reviews() for format.
 * @param $review
 *   Review array, see hook_review().
 * @return
 *   Array results, see do_coder_reviews() return value for format.
 */
function do_coder_review($coder_args, $review) {
  $results = array(
    '#stats' => array(
      'minor' => 0,
      'normal' => 0,
      'critical' => 0,
      'ignored' => 0,
    ),
  );
  $allowed_extensions = array_merge($coder_args['#php_extensions'], $coder_args['#include_extensions'], array(
    'module',
  ));
  if ($review['#rules']) {

    // Get the review's severity, used when the rule severity is not defined.
    $default_severity = isset($review['#severity']) ? _coder_review_severity($review['#severity']) : SEVERITY_NORMAL;

    // Get filename of current file.
    $filename = empty($coder_args['#patch']) ? $coder_args['#filename'] : 'coder_review.patch';
    $is_patch = 0;

    // Handle special case filename for patch files, if available.
    if (!empty($coder_args['#patch']) && preg_match('/(.+?):/', $coder_args['#filename'], $matches)) {
      $filename = empty($matches) ? 'coder_review.patch' : $matches[1];
      $is_patch = 1;
    }
    foreach ($review['#rules'] as $rule) {

      // Ignore rules that don't match the file extension.
      if ($filename_includes = isset($rule['#filename']) ? $rule['#filename'] : (isset($rule['#filename-not']) ? $coder_args['#php_extensions'] : NULL)) {
        $regex = '/(' . implode('|', $filename_includes) . ')$/i';
        if (!preg_match($regex, $filename, $matches)) {
          continue;
        }
      }

      // Ignore rules that do match the file extension, javascript files are excluded by default.
      $not = isset($rule['#filename-not']) ? $rule['#filename-not'] : (isset($rule['#filename']) ? NULL : $coder_args['#include_extensions']);
      if ($not) {
        $regex = '/(' . implode('|', $not) . ')$/i';

        // Check filename. If a patch, also check the .patch extension.
        if (preg_match($regex, $filename, $matches) || $is_patch && preg_match($regex, 'coder_review.patch', $matches)) {
          continue;
        }
      }

      // If it's a patch, it can contain any sort of file, so ensure the file
      // being processed is either php or one of the supported extensions.
      if ($is_patch) {
        $regex = '/(' . implode('|', $allowed_extensions) . ')$/i';
        if (!preg_match($regex, $filename, $matches)) {
          continue;
        }
      }

      // Perform the review if above the user requested severity.
      $severity = _coder_review_severity(isset($rule['#severity']) ? $rule['#severity'] : '', $default_severity);
      if ($severity >= $coder_args['#severity']) {
        if (isset($rule['#source'])) {

          // Values: all, html, comment, allphp or php.
          $source = '#' . $rule['#source'] . '_array_lines';
          $lines = isset($coder_args[$source]) ? $coder_args[$source] : array();
        }
        else {
          $lines = isset($coder_args['#php_array_lines']) ? $coder_args['#php_array_lines'] : array();
        }
        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 'grep_invert':
            do_coder_review_grep_invert($coder_args, $review, $rule, $lines, $results);
            break;
          case 'callback':
            do_coder_review_callback($coder_args, $review, $rule, $lines, $results);
            break;
        }
      }
    }
  }
  return $results;
}

/**
 * Implements do_coder_review_* for regex match.
 *
 * @param $coder_args
 *   Coder settings array variable, see do_coder_review() for format.
 * @param $review
 *   Review array the current rule belongs to, used by _coder_review_severity_name().
 * @param $rule
 *   Rule array being checked.
 * @param $lines
 *   Pertinent source file lines according to rule's '#source' value.
 * @param $results
 *   Results array variable to save errors to.
 */
function do_coder_review_regex(&$coder_args, $review, $rule, $lines, &$results) {
  if (isset($rule['#value']) && !empty($lines)) {
    $regexflags = isset($rule['#case-sensitive']) ? '' : 'i';
    $regex = '/' . $rule['#value'] . '/' . $regexflags;
    $class_regex = isset($rule['#class']) ? '/' . $rule['#class'] . '/' : '';
    $class_not_regex = isset($rule['#class-not']) ? '/' . $rule['#class-not'] . '/' : '';
    $class_current = '';
    $class_paren = 0;
    $function_regex = isset($rule['#function']) ? '/' . $rule['#function'] . '/' : '';
    $function_not_regex = isset($rule['#function-not']) ? '/' . $rule['#function-not'] . '/' : '';
    $function_current = '';
    $function_paren = 0;
    $not_regex = isset($rule['#not']) ? '/' . $rule['#not'] . '/' . $regexflags : '';
    $never_regex = isset($rule['#never']) ? '/' . $rule['#never'] . '/' . $regexflags : '';
    foreach ($lines as $lineno => $line_array) {
      foreach ($line_array as $line) {

        // Some rules apply only within certain functions.
        if ($function_regex || $function_not_regex) {
          if (preg_match('/function (\\w+)\\s*\\(/', $line, $match)) {
            $function_current = $match[1];
          }
          if (preg_match('/([{}])/', $line, $match)) {
            $function_paren += $match[0] == '{' ? 1 : -1;
          }
          if ($function_paren < 0 || $function_current == '' || $function_regex && !preg_match($function_regex, $function_current) || $function_not_regex && preg_match($function_not_regex, $function_current)) {
            continue;
          }
        }

        // Some rules apply only within certain classes.
        if ($class_regex || $class_not_regex) {
          if (preg_match('/class (\\w+)/', $line, $match) || preg_match('/interface (\\w+)/', $line, $match)) {
            $class_current = $match[1];
          }
          if (preg_match('/([{}])/', $line, $match)) {
            $class_paren += $match[0] == '{' ? 1 : -1;
          }
          if ($class_paren < 0 || $class_regex && $class_current == '' || $class_regex && !preg_match($class_regex, $class_current) || $class_not_regex && preg_match($class_not_regex, $class_current)) {
            continue;
          }
        }
        if (preg_match($regex, $line, $matches)) {

          // Don't match some regex's.
          if ($not_regex) {
            foreach ($matches as $match) {
              if (preg_match($not_regex, $match)) {
                continue 2;
              }
            }
          }
          if ($never_regex) {
            if (preg_match($never_regex, $coder_args['#all_lines'][$lineno])) {
              continue;
            }
          }
          $line = $coder_args['#all_lines'][$lineno];
          $severity_name = _coder_review_severity_name($coder_args, $review, $rule);
          if (!isset($coder_args['#ignore_lines'][$review['#review_name']])) {
            $coder_args['#ignore_lines'][$review['#review_name']] = array();
          }
          _coder_review_error($results, $rule, $severity_name, $lineno, $line, $coder_args['#ignore_lines'][$review['#review_name']]);
        }
      }
    }
  }
}

/**
 * Builds an error message based on the rule that failed and other information.
 *
 * @param $results
 *   Results array variable to save errors to.
 * @param $rule
 *   Rule array that triggered the error.
 * @param $severity_name
 *   String severity of error as detected by _coder_review_severity_name().
 * @param $lineno
 *   Line number of error.
 * @param $line
 *   Contents of line that triggered error.
 * @param $original
 *   Deprecated.
 */
function _coder_review_error(&$results, $rule, $severity_name, $lineno = -1, $line = '', $ignores = array()) {

  // Note: The use of the $key allows multiple errors on one line.
  // This assumes that no line of source has more than 10000 lines of code
  // and that we have fewer than 10000 errors.
  global $_coder_errno;

  // Skip warnings we've been told to ignore.
  if (is_array($ignores) && in_array($lineno, $ignores)) {
    $results['#stats']['ignored']++;
  }
  else {
    $key = ($lineno + 1) * 10000 + $_coder_errno++;
    $results[$key] = array(
      'rule' => $rule,
      'severity_name' => $severity_name,
      'lineno' => $lineno,
      'line' => $line,
    );
    $results['#stats'][$severity_name]++;
  }
}

/**
 * Search for a string.
 *
 * @see do_coder_review_regex()
 */
function do_coder_review_grep(&$coder_args, $review, $rule, $lines, &$results) {
  if (isset($rule['#value'])) {
    foreach ($lines as $lineno => $line_array) {
      foreach ($line_array as $line) {
        if (_coder_review_search_string($line, $rule)) {
          $line = $coder_args['#all_lines'][$lineno];
          $severity_name = _coder_review_severity_name($coder_args, $review, $rule);
          if (!isset($coder_args['#ignore_lines'][$review['#review_name']])) {
            $coder_args['#ignore_lines'][$review['#review_name']] = array();
          }
          _coder_review_error($results, $rule, $severity_name, $lineno, $line, $coder_args['#ignore_lines'][$review['#review_name']]);
        }
      }
    }
  }
}

/**
 * Search for potentially missing string.
 *
 * @see do_coder_review_regex()
 */
function do_coder_review_grep_invert(&$coder_args, $review, $rule, $lines, &$results) {
  if (isset($rule['#value'])) {
    foreach ($lines as $lineno => $line_array) {
      foreach ($line_array as $line) {
        if (_coder_review_search_string($line, $rule)) {
          return;
        }
      }
    }
    $severity_name = _coder_review_severity_name($coder_args, $review, $rule);
    _coder_review_error($results, $rule, $severity_name);
  }
}

/**
 * Allow for an arbitrary callback function to perform a review.
 *
 * @see do_coder_review_regex()
 */
function do_coder_review_callback(&$coder_args, $review, $rule, $lines, &$results) {
  if ($function = $rule['#value']) {
    if (function_exists($function)) {
      call_user_func_array($function, array(
        &$coder_args,
        $review,
        $rule,
        $lines,
        &$results,
      ));
    }
  }
}

/**
 * Search for a string.
 *
 * Uses the fastest available php function for searching.
 *
 * @param $line
 *   Haystack.
 * @param $rule
 *   Rule to process.
 * @return
 *   TRUE if needle is in haystack.
 */
function _coder_review_search_string($line, $rule) {
  static $php5;
  if (!isset($php5)) {
    if (function_exists('stripos')) {
      $php5 = TRUE;
    }
    else {
      $php5 = FALSE;
    }
  }

  // Case-sensitive search with strpos() (supported everywhere).
  if (isset($rule['#case-sensitive'])) {
    return strpos($line, $rule['#value']) !== FALSE;
  }

  // Case-insensitive search with stripos() (supported in PHP 5).
  if ($php5 && !isset($rule['#case-sensitive'])) {
    return stripos($line, $rule['#value']) !== FALSE;
  }

  // Case-insensitive search.
  $regex = '/' . preg_quote($rule['#value']) . '/i';
  return preg_match($regex, $line);
}

/**
 * Return true if $module is in Drupal core.
 */
function _coder_review_is_drupal_core($system) {
  $info_file = dirname(realpath($system->filename)) . '/' . $system->name . '.info';
  $info = drupal_parse_info_file($info_file);
  return !empty($info['package']) && strtolower($info['package']) == 'core';
}

/**
 * Get list of lines to ignore.
 */
function coder_review_get_ignores() {
  $ignores = coder_review_parse_ignores(drupal_get_path('module', 'coder_review') . '/core.coder_review_ignores.txt');
  foreach (module_implements('coder_review_ignore') as $module) {
    $function = $module . '_coder_review_ignore';
    $default_info = array(
      'path' => '',
      'line prefix' => '',
    );
    $info = array_merge($default_info, $function());
    $ignores += coder_review_parse_ignores($info['path'] . '/' . $module . '.coder_review_ignores.txt', $info['line prefix']);
  }
  return $ignores;
}

/**
 * Parse an 'ignore' file.
 */
function coder_review_parse_ignores($filepath, $line_prefix = '') {
  $ignores = array();

  // Ensure the file exists.
  if (!is_file($filepath) || !is_readable($filepath)) {
    drupal_set_message(t("File %file doesn't exist or isn't readable.", array(
      '%file' => $filepath,
    )), 'error');
    return array();
  }

  // Read in the file contents.
  $lines = file($filepath);

  // Parse the contents.
  foreach ($lines as $line) {
    $line = trim($line);
    if (empty($line) || preg_match('/^;/', $line, $matches)) {
      continue;
    }
    $line = $line_prefix . $line;

    // filename:lineo:review
    $parts = explode(':', $line);

    // $ignores[filename][review][] = lineno
    $ignores[$parts[0]][$parts[2]][] = $parts[1];
  }
  return $ignores;
}

/**
 * Implements hook_simpletest().
 */
function coder_review_simpletest() {
  return array_keys(file_scan_directory(drupal_get_path('module', 'coder_review') . '/tests', '/\\.test/'));
}

// Theming functions

/**
 * Implements hook_theme().
 */
function coder_review_theme() {
  return array(
    'coder_review' => array(
      'variables' => array(
        'name' => NULL,
        'filename' => NULL,
        'results' => NULL,
      ),
    ),
    'coder_review_warning' => array(
      'variables' => array(
        'warning' => NULL,
        'severity_name' => NULL,
        'lineno' => NULL,
        'line' => NULL,
      ),
    ),
    'coder_review_warning_msg' => array(
      'variables' => array(
        'error' => NULL,
      ),
    ),
    'coder_review_table_cols' => array(
      'render element' => 'form',
    ),
    'drupalapi' => array(
      'variables' => array(
        'function' => NULL,
        'version' => drupal_substr(VERSION, 0, 1),
      ),
    ),
    'phpapi' => array(
      'variables' => array(
        'function' => NULL,
      ),
    ),
  );
}

/**
 * Format coder_review form and results.
 *
 * @param $name
 *   Name of module/theme checked, not used.
 * @param $filename
 *   String filename checked.
 * @param $results
 *   Array list of results HTML to display. See do_coder_reviews() for format.
 */
function theme_coder_review($variables) {

  // theme the output for the Drupal shell
  if (function_exists('_coder_review_drush_is_option') && _coder_review_drush_is_option('drush')) {
    return theme_drush_coder_review($variables);
  }
  $name = $variables['name'];
  $filename = $variables['filename'];
  $results = $variables['results'];
  $output = '<div class="coder"><h2>' . basename($filename) . '</h2>';
  if (!empty($results)) {
    $output .= theme('item_list', array(
      'items' => $results,
    ));
  }
  $output .= '</div>';
  return $output;
}

/**
 * Format a coder_review warning to be included in results, creating the text.
 *
 * @param $results
 *   Results array variable to save errors to.
 * @param $error
 *   Error array from _coder_review_error().
 */
function theme_coder_review_warning_msg($variables) {
  $error = $variables['error'];

  // @TODO: we can combine theme_coder_review_warning() and theme_coder_review_warning_msg(),
  // But let's do this on the 7.x upgrade in case anyone's actually using it.
  return theme('coder_review_warning', array(
    'warning' => _coder_review_warning($error['rule']),
    'severity_name' => $error['severity_name'],
    'lineno' => $error['lineno'],
    'line' => $error['line'],
  ));
}

/**
 * Return the formatted warning message from the $rule.
 */
function _coder_review_warning($rule) {
  if (isset($rule['#warning_callback'])) {
    if (function_exists($rule['#warning_callback'])) {
      return $rule['#warning_callback']();
    }
    return t('please <a href="@report">report</a> this !warning', array(
      '@report' => 'http://drupal.org/node/add/project_issue/coder/bug',
      '!warning' => $rule['#warning_callback'],
    ));
  }
  return t($rule['#warning']);
}

/**
 * Format a coder_review warning to be included in results.
 *
 * @param $warning
 *   Either summary warning description, or an array in format:
 *     - '#warning' => Summary warning description;
 *     - '#description' => Detailed warning description;
 *     - '#link' => Link to an explanatory document.
 * @param $severity_name
 *   String severity name.
 * @param $lineno
 *   Integer line number of error.
 * @param $line
 *   String contents of line.
 */
function theme_coder_review_warning($variables) {

  // theme the output for the Drupal shell
  if (function_exists('_coder_review_drush_is_option') && _coder_review_drush_is_option('drush')) {
    return theme_drush_coder_review_warning($variables);
  }
  $warning = $variables['warning'];
  $severity_name = $variables['severity_name'];
  $lineno = $variables['lineno'];
  $line = $variables['line'];

  // Extract description from warning.
  if (is_array($warning)) {
    $description = isset($warning['#description']) ? $warning['#description'] : '';
    $warning_msg = $warning['#warning'];
    if (isset($warning['#link'])) {
      $warning_msg .= ' (' . l(t('Drupal Docs'), $warning['#link']) . ')';
    }
    $warning = $warning_msg;
  }
  if ($lineno) {
    $warning = t('Line @number: !warning', array(
      '@number' => $lineno,
      '!warning' => $warning,
    ));
    if ($line) {
      $warning .= '<pre>' . check_plain($line) . '</pre>';
    }
  }
  $class = 'coder-warning';
  if ($severity_name) {
    $class .= " coder-{$severity_name}";
  }
  $path = drupal_get_path('module', 'coder');
  $title = t('severity: @severity', array(
    '@severity' => $severity_name,
  ));
  $img = theme('image', array(
    'path' => $path . "/images/{$severity_name}.png",
    'alt' => $title,
    'title' => $title,
    'attributes' => array(
      'align' => 'right',
      'class' => 'coder',
    ),
    'getsize' => FALSE,
  ));
  if (!empty($description)) {
    $img .= theme('image', array(
      'path' => $path . '/images/more.png',
      'alt' => t('click to read more'),
      'atributes' => array(
        'align' => 'right',
        'class' => 'coder-more',
      ),
      'getsize' => FALSE,
    ));
    $warning .= '<div class="coder-description">Explanation: ' . $description . '</div>';
  }
  return '<div class="' . $class . '">' . $img . $warning . '</div>';
}

/**
 * Format link to Drupal API.
 *
 * @param $function
 *   Function to link to.
 * @param $version
 *   Version to link to.
 */
function theme_drupalapi($variables) {
  $version = $variables['version'];
  $function = $variables['function'];
  return l($function, "http://api.drupal.org/api/function/{$function}/{$version}");
}

/**
 * Format link to PHP documentation.
 *
 * @param $function
 *   Function to link to.
 */
function theme_phpapi($variables) {
  $function = $variables['function'];
  return l($function, "http://php.net/{$function}");
}

Functions

Namesort descending Description
coder_review_admin_settings Configuration page.
coder_review_flush_caches Implements hook_flush_caches().
coder_review_form_alter Implements hook_form_alter().
coder_review_get_ignores Get list of lines to ignore.
coder_review_help Implements hook_help().
coder_review_menu Implements hook_menu().
coder_review_menu_alter Implements hook_menu_alter().
coder_review_page Code review page.
coder_review_page_form Implements hook_form().
coder_review_page_form_submit Implements hook_submit().
coder_review_parse_ignores Parse an 'ignore' file.
coder_review_permission Implements hook_permission().
coder_review_reviews Implements hook_reviews().
coder_review_settings_form_submit Callback function for settings page.
coder_review_simpletest Implements hook_simpletest().
coder_review_theme Implements hook_theme().
do_coder_review Perform code review for a review array.
do_coder_reviews Perform batch coder reviews for multiple files.
do_coder_review_callback Allow for an arbitrary callback function to perform a review.
do_coder_review_grep Search for a string.
do_coder_review_grep_invert Search for potentially missing string.
do_coder_review_regex Implements do_coder_review_* for regex match.
theme_coder_review Format coder_review form and results.
theme_coder_review_table_cols Implement theme_cols to theme the radiobuttons and checkboxes form elements in a table column.
theme_coder_review_warning Format a coder_review warning to be included in results.
theme_coder_review_warning_msg Format a coder_review warning to be included in results, creating the text.
theme_drupalapi Format link to Drupal API.
theme_phpapi Format link to PHP documentation.
_coder_review_default_reviews Helper functions for settings form.
_coder_review_error Builds an error message based on the rule that failed and other information.
_coder_review_form_output
_coder_review_get_default_settings Returns a active settings array for coder_review.
_coder_review_get_reviews_extensions Return the extensions used by the reviews, that aren't part of the default extensions.
_coder_review_is_drupal_core Return true if $module is in Drupal core.
_coder_review_modified Return last modification timestamp of coder_review and all of its dependencies.
_coder_review_page_form_includes Add results to form array for display on form page.
_coder_review_read_and_parse_file Parse and read a file into a format easy to validate.
_coder_review_reviews Get all of the code review modules, including contributions.
_coder_review_search_string Search for a string.
_coder_review_settings_array Generate settings array for either modules or themes.
_coder_review_settings_form Build settings form API array for coder_review.
_coder_review_severity Return the integer severity magic number for a string severity.
_coder_review_severity_name Return string severity for a given error.
_coder_review_warning Return the formatted warning message from the $rule.

Constants