You are here

coder.module in Coder 5.2

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:

Credit also to dries:

File

coder.module
View source
<?php

if (module_exists('drush')) {
  include drupal_get_path('module', 'coder') . '/coder.drush.inc';
}

/**
 * @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
 */

/**
 * Implementation of hook_help().
 */
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',
      ));
    case 'drush:coder':
      return t('coder [summary] [<styles>] [minor|major|critical] [active|core|all|default|<modules>] [no-<module>]');
  }
}

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

/**
 * Implementation of hook_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';
      if (function_exists($function)) {
        if ($review = call_user_func($function)) {
          $_coder_reviews = array_merge($_coder_reviews, $review);
        }
      }
    }
  }
  return $_coder_reviews;
}

/**
 * Implementation of hook_cron().
 */
function coder_cron() {
  if ($use_cache = variable_get('coder_cache', 1)) {

    // TODO: move some of the work here... is this really worth it?
  }
}

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

/**
 * Implementation of hook_menu().
 */
function coder_menu($may_cache = TRUE) {
  $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'),
    );
  }
  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}") . ')';
      }
    }
  }
}

/**
 * Helper functions for settings form.
 */
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.
 */
function _coder_settings_form($settings, &$system, &$files) {

  // Add the javascript.
  $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.
  $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'];
  }

  // 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(
      1 => 'minor (most)',
      5 => 'normal',
      9 => 'critical (fewest)',
    ),
    '#description' => t('show warnings at or above the severity warning level'),
    '#default_value' => $settings['coder_severity'],
  );

  // 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();
  while ($system = db_fetch_object($result)) {
    $display_name = $system->name;
    if ($system->status) {
      $display_name .= t(' (active)');
      $system_active[$system->name] = $system->name;
    }
    if (_coder_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, "coder/{$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)'),
  );
  $form['coder_what']['coder_includes'] = array(
    '#type' => 'checkbox',
    '#default_value' => $settings['coder_includes'],
    '#title' => t('include files (.inc and .php files)'),
  );
  if (arg(0) == 'admin') {
    $form['coder_what']['coder_cache'] = array(
      '#type' => 'checkbox',
      '#default_value' => $settings['coder_cache'],
      '#title' => t('use the coder cache'),
    );
  }

  // 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' => '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);
  }
  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_checkboxes',
    ),
  );
  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);
  }
  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' => implode(' ', $classes),
      ),
    );
  }
  $system = array_merge($modules, $themes);
  return $form;
}
if (!function_exists('theme_cols')) {

  /**
   * Implement theme_cols to theme the radiobuttons and checkboxes form
   * elements in a table column.
   */
  function theme_cols($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);
      }
    }
    return theme('table', array(), $rows);
  }
}

/**
 * Implementation of settings page for Drupal 5.
 */
function coder_admin_settings() {
  $settings = _coder_get_default_settings();
  $form = _coder_settings_form($settings, $system, $files);
  $form['help'] = array(
    '#type' => 'markup',
    '#value' => t('After setting these defaults, use <a href="@url">coder</a> to perform code reviews.', array(
      '@url' => url('coder'),
    )),
    '#weight' => -1,
  );
  $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.
 */
function coder_settings_form_submit($form_id, &$form_values) {
  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
 */
function _coder_settings_array(&$form_values, $type) {
  $typekey = "coder_{$type}s-";
  $typelen = strlen($typekey);
  $systems = array();
  foreach ($form_values as $key => $value) {
    if (substr($key, 0, $typelen) == $typekey) {
      if ($value == 1) {
        $system = substr($key, $typelen);
        $systems[$system] = 1;
      }
      unset($form_values[$key]);
    }
  }
  return $systems;
}

/**
 * Implementation of code review page.
 */
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') {
  $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);

  // Determine any options based on the passed in URL.
  switch ($args) {
    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 '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_modules'] = variable_get('coder_modules', array());
      $settings['coder_themes'] = variable_get('coder_themes', array());
      break;
    default:
      $settings['coder_includes'] = 1;

      // TODO: does this need to go into coder_themes sometimes?
      $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) {
  if (isset($form_values['op'])) {
    $settings = $form_values;
    $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)'));
  }
  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',
      )));
    }
  }

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

    // Add style sheet.
    $path = drupal_get_path('module', 'coder');
    drupal_add_css($path . '/coder.css', 'module');

    // Code review non-module core files.
    $module_weight = 0;
    if (isset($settings['coder_core']) && $settings['coder_core']) {
      $coder_args = array(
        '#reviews' => $reviews,
        '#severity' => $settings['coder_severity'],
      );
      $form['core_php'] = array(
        '#type' => 'fieldset',
        '#title' => 'core (php)',
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
        '#weight' => ++$module_weight,
      );
      $phpfiles = file_scan_directory('.', '.*\\.php', array(
        '.',
        '..',
        'CVS',
      ), 0, FALSE, 'name', 0);
      $stats['core']['#includes'] = _coder_page_form_includes($form, $coder_args, 'core_php', $phpfiles, 2);
      $form['core_includes'] = array(
        '#type' => 'fieldset',
        '#title' => 'core (includes)',
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
        '#weight' => ++$module_weight,
      );
      $includefiles = drupal_system_listing('.*\\.inc$', 'includes', 'filename', 0);
      $stats['core']['#includes'] = _coder_page_form_includes($form, $coder_args, 'core_includes', $includefiles, 0);
    }

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

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

          // Process this one file.
          $filename = $files[$name];
          if (!$filename) {
            drupal_set_message(t('Code Review file for %module not found', array(
              '%module' => $name,
            )), 'error');
            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']);

          // 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'] = array(
            '#value' => theme('coder', $name, $filename, $results),
            '#weight' => -1,
          );

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

            // NOTE: Convert to the realpath here so drupal_system_listing
            // doesn't return additional paths (i.e., try "module").
            if ($path = str_replace('\\', '/', dirname(realpath($filename)))) {
              $offset = strpos($path, dirname($filename));
              if (!isset($dups[$path])) {
                if (substr($filename, -7) == '.module') {
                  $coder_args['#php_minor'] = 1;
                }
                $dups[$path] = 1;
                $includefiles = drupal_system_listing('.*\\.(inc|php|install|test|schema)$', $path, 'filename', 0);
                $stats[$filename]['#includes'] = _coder_page_form_includes($form, $coder_args, $name, $includefiles, $offset);
              }
            }
          }
        }
      }
      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));
        if (function_exists('_coder_drush_is_option') && _coder_drush_is_option('drush')) {
          print coder_print_drush_messages();
        }
      }
    }

    // Prepend the settings form.
    $form['settings'] = array(
      '#type' => 'fieldset',
      '#title' => t('Selection Form'),
      '#collapsible' => TRUE,
      '#collapsed' => isset($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/settings/coder'),
        '@default' => url('coder/default'),
      ));
    }
    $form['settings'][] = $coder_form;
    $form['settings']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Submit'),
    );
    $form['#multistep'] = TRUE;
    $form['#redirect'] = FALSE;
  }
  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) {
  $stats = array();
  $coder_args['#name'] = $name;
  $weight = 0;
  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,
      '#weight' => ++$weight,
    );
    if (empty($results)) {
      $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 $stats;
}

/**
 * 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 && _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;
    }
  }

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

    // Do all of the code reviews.
    foreach ($coder_args['#reviews'] as $review) {
      if ($result = do_coder_review($coder_args, $review)) {
        foreach (array(
          'critical',
          'normal',
          'minor',
        ) as $severity_level) {
          if (isset($result['#stats'][$severity_level])) {
            $results['#stats'][$severity_level] += $result['#stats'][$severity_level];
          }
        }
        $results += $result;
      }
    }

    // Sort the results.
    ksort($results, SORT_NUMERIC);
  }
  else {
    _coder_error_msg($results, t('Could not read the file'), 'critical');
  }

  // Save the results in the cache.
  if ($use_cache) {
    $cache_results = array(
      'mtime' => $cache_mtime,
      'results' => $results,
    );
    cache_set($cache_key, 'cache', serialize($cache_results));
  }
  return $results;
}

/**
 * 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) {

  // Get the path to the module file.
  if ($filepath = realpath($coder_args['#filename'])) {

    // Read the file.
    $content = file_get_contents($filepath) . "\n";
    $content_length = drupal_strlen($content);
    $in_comment = 0;
    $beginning_of_line = 0;
    $in_php = 0;
    $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();
    $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 = '';
    $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++) {

      // Get the current character.
      $char = $content[$pos];
      if ($char == "\n") {
        if ($in_comment && $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 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] = $this_all_lines;
          }
          if (trim($this_php_lines, "\r\n") != '') {
            $php_lines[$lineno] = $this_php_lines;
          }
          if (trim($this_allphp_lines, "\r\n") != '') {
            $allphp_lines[$lineno] = $this_allphp_lines;
          }
          if (trim($this_html_lines, "\r\n") != '') {
            $html_lines[$lineno] = $this_html_lines;
          }
          if (trim($this_quote_lines, "\r\n") != '') {
            $quote_lines[$lineno] = $this_quote_lines;
          }
          if (trim($this_doublequote_lines, "\r\n") != '') {
            $doublequote_lines[$lineno] = $this_doublequote_lines;
          }
          if (trim($this_comment_lines, "\r\n") != '') {
            $comment_lines[$lineno] = $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 = '';
        $this_doublequote_lines = '';
        $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) {
            $this_quote_lines .= $char;
            if ($in_quote == '"') {
              $this_doublequote_lines .= $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] && 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) {
            $this_html_lines .= $char;
          }
          if ($in_heredoc_html && $char == $in_heredoc_html) {
            $in_heredoc_html = '';
          }
          unset($char);
        }
        elseif ($char == '?' && $content[$pos + 1] == '>') {
          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 '"':
              if ($content[$pos - 1] != '\\') {
                $this_php_lines .= $char;
                $in_quote = $char;
              }
              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 (substr($content, $pos + 2, 3) == 'php') {
                $in_php = 1;
                $in_allphp = 1;
                $this_all_lines .= '?php';
                $pos += 4;
              }
              break;
            }

          // FALTHROUGH
          default:
            $this_html_lines .= $char;
            break;
        }
      }
    }

    // Add the files lines to the arguments.
    $coder_args['#all_lines'] = $all_lines;
    $coder_args['#php_lines'] = $php_lines;
    $coder_args['#allphp_lines'] = $allphp_lines;
    $coder_args['#html_lines'] = $html_lines;
    $coder_args['#quote_lines'] = $quote_lines;
    $coder_args['#doublequote_lines'] = $doublequote_lines;
    $coder_args['#comment_lines'] = $comment_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 to use if severity string is not recognized.
 * @return
 *   Integer.
 */
function _coder_severity($severity_name, $default_value = 5) {

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

/**
 * 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_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']) && 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 settings, must have been prepared with _coder_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();
  if ($review['#rules']) {

    // Get the review's severity, used when the rule severity is not defined.
    $default_severity = _coder_severity($review['#severity']);
    foreach ($review['#rules'] as $rule) {

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

          // Deprecated.
          $lines = $coder_args['#all_lines'];
        }
        elseif (isset($rule['#source'])) {

          // Values: all, html, comment, allphp or php.
          $source = '#' . $rule['#source'] . '_lines';
          $lines = $coder_args[$source];
        }
        else {
          $lines = $coder_args['#php_lines'];
        }
        if ($lines) {
          switch ($rule['#type']) {
            case 'regex':
              do_coder_review_regex($coder_args, $review, $rule, $lines, $results);
              break;
            case 'grep':
              do_coder_review_grep($coder_args, $review, $rule, $lines, $results);
              break;
            case '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_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'])) {
    $regexflags = isset($rule['#case-sensitive']) ? '' : 'i';
    $regex = '/' . $rule['#value'] . '/' . $regexflags;
    $function_regex = isset($rule['#function']) ? '/' . $rule['#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) {

      // Some rules apply only within certain functions.
      if ($function_regex) {
        if (preg_match('/function (\\w+)\\(/', $line, $match)) {
          $current_function = $match[1];
        }
        if (preg_match('/([{}])/', $line, $match)) {
          $paren += $match[0] == '{' ? 1 : -1;
        }
        if ($paren < 0 || $current_function == '' || !preg_match($function_regex, $current_function)) {
          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_severity_name($coder_args, $review, $rule);
        _coder_error($results, $rule, $severity_name, $lineno, $line);
      }
    }
  }
}

/**
 * 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_severity_name().
 * @param $lineno
 *   Line number of error.
 * @param $line
 *   Contents of line that triggered error.
 * @param $original
 *   Deprecated.
 */
function _coder_error(&$results, $rule, $severity_name, $lineno = -1, $line = '', $original = '') {
  if (isset($rule['#warning_callback'])) {
    if (function_exists($rule['#warning_callback'])) {
      $warning = $rule['#warning_callback']();
    }
    else {

      // If this happens, there is an error in the rule definition.
      $warning = t('please <a href="@report">report</a> this !warning', array(
        '@report' => 'http://drupal.org/node/add/project_issue/coder/bug',
        '!warning' => $rule['#warning_callback'],
      ));
    }
  }
  else {
    $warning = t($rule['#warning']);
  }
  return _coder_error_msg($results, $warning, $severity_name, $lineno, $line);
}

/**
 * Does the actual saving of error to results array and generating its
 * unique numeric id.
 *
 * @param $results
 *   Results array variable to save errors to.
 * @param $warning
 *   Warning array/string to be themed, returned from '#warning_callback' or
 *   is a translated string from the rule. See theme_coder_warning() for
 *   array format.
 * @param $severity_name
 *   String severity of error.
 * @param $lineno
 *   Integer line number error occured on.
 * @param $line
 *   String line contents.
 */
function _coder_error_msg(&$results, $warning, $severity_name, $lineno = -1, $line = '') {

  // 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;
  $key = ($lineno + 1) * 10000 + $_coder_errno++;
  $results[$key] = theme('coder_warning', $warning, $severity_name, $lineno + 1, $line);
  $results['#stats'][$severity_name]++;
}

/**
 * Search for a string.
 *
 * @note
 *   See do_coder_review_regex() for arguments.
 */
function do_coder_review_grep(&$coder_args, $review, $rule, $lines, &$results) {
  if (isset($rule['#value'])) {
    foreach ($lines as $lineno => $line) {
      if (_coder_search_string($line, $rule)) {
        $line = $coder_args['#all_lines'][$lineno];
        $severity_name = _coder_severity_name($coder_args, $review, $rule);
        _coder_error($results, $rule, $severity_name, $lineno, $line);
      }
    }
  }
}

/**
 * Search for potentially missing string.
 *
 * @note
 *   See do_coder_review_regex() for arguments.
 */
function do_coder_review_grep_invert(&$coder_args, $review, $rule, $lines, &$results) {
  if (isset($rule['#value'])) {
    foreach ($lines as $lineno => $line) {
      if (_coder_search_string($line, $rule)) {
        return;
      }
    }
    $severity_name = _coder_severity_name($coder_args, $review, $rule);
    _coder_error($results, $rule, $severity_name);
  }
}

/**
 * Allow for an arbitrary callback function to perform a review.
 *
 * @note
 *   See do_coder_review_regex() for arguments.
 */
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_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_is_drupal_core($module) {
  static $core;
  if (!isset($core)) {
    $core = array(
      // Modules:
      'aggregator' => 1,
      'block' => 1,
      'blog' => 1,
      'blogapi' => 1,
      'book' => 1,
      'color' => 1,
      'comment' => 1,
      'contact' => 1,
      'dblog' => 1,
      'drupal' => 1,
      'filter' => 1,
      'forum' => 1,
      'help' => 1,
      'locale' => 1,
      'menu' => 1,
      'node' => 1,
      'path' => 1,
      'php' => 1,
      'ping' => 1,
      'poll' => 1,
      'profile' => 1,
      'search' => 1,
      'statistics' => 1,
      'syslog' => 1,
      'system' => 1,
      'taxonomy' => 1,
      'throttle' => 1,
      'tracker' => 1,
      'upload' => 1,
      'user' => 1,
      // Themes:
      'bluemarine' => 1,
      'chameleon' => 1,
      'garland' => 1,
      'marvin' => 1,
      'minnelli' => 1,
      'pushbutton' => 1,
    );
  }
  return isset($core[$module->name]) ? 1 : 0;
}

// Theming functions

/**
 * Format coder 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($name, $filename, $results) {

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

/**
 * Format a coder 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_warning($warning, $severity_name, $lineno = 0, $line = '') {

  // theme the output for the Drupal shell
  if (function_exists('_coder_drush_is_option') && _coder_drush_is_option('drush')) {
    return theme_drush_coder_warning($warning, $severity_name, $lineno, $line);
  }

  // Extract description from warning.
  if (is_array($warning)) {
    $description = $warning['#description'];
    $link = $warning['#link'];
    $warning = $warning['#warning'];
    if (isset($link)) {
      $warning .= '  (' . l('Drupal Docs', $link) . ')';
    }
  }
  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', $path . "/images/{$severity_name}.png", $title, $title, array(
    'align' => 'right',
    'class' => 'coder',
  ), FALSE);
  if (isset($description)) {
    $img .= theme('image', $path . '/images/more.png', t('click to read more'), '', array(
      'align' => 'right',
      'class' => 'coder-more',
    ), 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($function, $version = '') {
  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($function) {
  return l($function, "http://us.php.net/{$function}");
}

Functions

Namesort descending Description
coder_admin_settings Implementation of settings page for Drupal 5.
coder_cron Implementation of hook_cron().
coder_form_alter Implementation of hook_form_alter().
coder_help Implementation of hook_help().
coder_menu Implementation of hook_menu().
coder_page Implementation of code review page.
coder_page_form Implementation of hook_form().
coder_perm Implementation of hook_perm().
coder_reviews Implementation of hook_reviews().
coder_settings_form_submit Callback function for settings page in Drupal 5.
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 Format coder form and results.
theme_coder_warning Format a coder warning to be included in results.
theme_drupalapi Format link to Drupal API.
theme_phpapi Format link to PHP documentation.
_coder_default_reviews Helper functions for settings form.
_coder_error Builds an error message based on the rule that failed and other information.
_coder_error_msg Does the actual saving of error to results array and generating its unique numeric id.
_coder_get_default_settings Returns a active settings array for coder.
_coder_is_drupal_core Return true if $module is in Drupal core.
_coder_modified Return last modification timestamp of coder and all of its dependencies.
_coder_page_form_includes Add results to form array for display on form page.
_coder_read_and_parse_file Parse and read a file into a format easy to validate.
_coder_reviews Get all of the code review modules, including contributions.
_coder_search_string Search for a string.
_coder_settings_array Generate settings array for either modules or themes.
_coder_settings_form Build settings form API array for coder.
_coder_severity Return the integer severity magic number for a string severity.
_coder_severity_name Return string severity for a given error.