You are here

coder_review.common.inc in Coder 7.2

Common functions used by both the drush and form interfaces.

File

coder_review/coder_review.common.inc
View source
<?php

/**
 * @file
 * Common functions used by both the drush and form interfaces.
 */

/**
 * Specifies the Minor severity level.
 */
define('SEVERITY_MINOR', 1);

/**
 * Specifies the Normal severity level.
 */
define('SEVERITY_NORMAL', 5);

/**
 * Specifies the Critical severity level.
 */
define('SEVERITY_CRITICAL', 9);

/**
 * Creates a list of all modules that implement hook_reviews().
 *
 * @return array
 *   An array of return values of the from  hook_reviews() implementations.
 */
function _coder_review_reviews() {
  static $cache = array();
  if (!$cache) {

    // Always get the coder review.
    $cache = coder_review_reviews();

    // When Drupal is bootstrapped, also get all reviews.
    if (function_exists('module_invoke_all')) {
      $cache = array_merge($cache, module_invoke_all('reviews'));
    }
  }
  return $cache;
}

/**
 * Implements hook_reviews().
 *
 * This creates an array of all of the reviews that are implemented by the
 * coder_review module.
 *
 * @see hook_reviews()
 *
 * @todo Add an abbreviated explanation of what is coder_review.api.php.
 */
function coder_review_reviews() {
  static $cache = array();
  if (!$cache) {
    $path = _coder_review_file_path() . '/includes';
    foreach (scandir($path) as $file) {
      $pathinfo = pathinfo("{$path}/{$file}");
      if ($pathinfo['extension'] == 'inc') {
        require_once "{$path}/{$file}";
        $function = $pathinfo['filename'] . '_reviews';
        if (function_exists($function)) {
          $review = $function();
          if ($review) {
            foreach (array_keys($review) as $review_name) {
              $review[$review_name]['#file'] = "{$path}/{$file}";
            }
            $cache += $review;
          }
        }
      }
    }
  }
  return $cache;
}

/**
 * Returns a default list of reviews to perform a coder_review analysis upon.
 *
 * @return array
 *   An associative array with keys set to the names of the reviews to perform.
 *   The values are set to ???
 *
 * @see _coder_review_get_default_settings()
 */
function _coder_review_default_reviews() {

  // Do not use drupal_map_assoc() so that this will run without Drupal being
  // bootstraped.
  return array(
    'style' => 'style',
    'sql' => 'sql',
    'comment' => 'comment',
    'security' => 'security',
    'i18n' => 'i18n',
    'sniffer' => 'sniffer',
  );
}

/**
 * Creates a list of required filename extensions the included in default list.
 *
 * @param array $defaults
 *   An array of default filename extensions.
 * @param array $reviews
 *   An array of available coder reviews.
 *
 * @return array
 *   An array of file extensions, which are required to perform any of the
 *   reviews, but arenot part of the default filename extension list.
 *
 * @see coder_review_page_form()
 * @see coder_review_test_case.tinc::runTest()
 */
function _coder_review_get_reviews_extensions(array $defaults, array $reviews) {
  $extensions = array();
  foreach ($reviews as $key => $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;
}

/**
 * Determines most recent modification timestamp of key coder_review code files.
 *
 * @return int
 *   A Unix timestamp that represents the latest time that any of the key code
 *   files that are part of the coder_review module was modified.
 */
function _coder_review_modified() {
  static $cached_mtime = 0;
  if (!$cached_mtime) {

    // Create a list of all files whose timestamps are worth checking.
    $files[] = _coder_review_file_path() . '/coder_review.module';
    $files[] = _coder_review_file_path() . '/coder_review.common.inc';
    foreach (coder_review_reviews() as $review) {
      $files[] = $review['#file'];
    }

    // Find the newest filetime.
    foreach ($files as $file) {
      $mtime = filemtime($file);
      if ($mtime > $cached_mtime) {
        $cached_mtime = $mtime;
      }
    }
  }
  return $cached_mtime;
}

/**
 * Performs coder reviews for multiple code review definition files.
 *
 * @param array $coder_args
 *   An associative array of coder arguments. The valid arguments are:
 *   - #reviews => An array list of reviews to perform.  For more information,
 *     see _coder_review_reviews().
 *   - #severity => An integer magic number. See the constants SEVERITY_*, as
 *     defined at the top of coder_review.common.inc.
 *   - #filename => A string with the filename to check.
 *   - #patch => A string with the patch lines to perform a review on.
 *
 * @return array
 *   An associative array of results, in the form:
 *   - #stats => An array with error counts for all severities, in the form:
 *     - 'minor' => An integer count.
 *     - 'normal' => An integer count.
 *     - 'critical' => An integer count.
 *   - integer ID => An HTML error message string for display.
 *
 * @see _coder_review_reviews()
 */
function do_coder_reviews(array $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']) {

    // @todo Replace md5() with with sha256().  See [#723802] for more info.
    $cache_key = 'coder:' . md5(implode(':', array_keys($coder_args['#reviews']))) . $coder_args['#severity'] . ':' . $coder_args['#filename'];
    if (_drush()) {
      if (drush_get_option('checkstyle')) {
        $cache_key .= ':drushcheckstyle';
      }
      elseif (drush_get_option('xml')) {
        $cache_key .= ':drushxml';
      }
    }
    $filepath = realpath($coder_args['#filename']);
    if (file_exists($filepath)) {
      $cache_mtime = filemtime($filepath);
      $cache_results = _cache_get($cache_key, 'cache_coder');
      if ($cache_results && $cache_results->data['mtime'] == $cache_mtime && _coder_review_modified() < $cache_results->created) {
        return $cache_results->data['results'];
      }
    }
  }
  $results['#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']) && _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;
      $result = do_coder_review($coder_args, $review);
      if ($result) {

        // Keep track of severity and "ignored" statistics.
        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($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;
}

/**
 * Returns HTML for a coder_review warning to be included in results.
 *
 * @param array $variables
 *   An associative array, which includes the following keys:
 *   - warning: Either summary warning description, or an array in format:
 *     - #warning: A summary warning description.
 *     - #description: A detailed warning description.
 *     - #link: A link to an explanatory document.
 *   - severity_name: The severity name as a string.
 *   - lineno: An integer line number on which error was detected.
 *   - line: A string with the contents of line.
 *   - review_name: A string with the review name.
 *   - rule_name: A string with the rule name.
 *
 * @ingroup themeable
 */
function theme_coder_review_warning($variables) {

  // Theme the output for the Drupal shell.
  if (_drush()) {
    return theme_drush_coder_review_warning($variables);
  }
  $warning = $variables['warning'];
  $severity_name = $variables['severity_name'];
  $review_name = $variables['review_name'];
  $rule_name = $variables['rule_name'];
  $lineno = $variables['lineno'];
  $line = $variables['line'];

  // Extract description from warning.
  if (is_array($warning)) {
    $description = isset($warning['#description']) ? _t($warning['#description']) : '';
    if (isset($warning['#warning'])) {
      $warning_msg = _t($warning['#warning']);
    }
    elseif (isset($warning['#text'])) {
      $warning += array(
        '#args' => array(),
      );
      $warning_msg = _t($warning['#text'], $warning['#args']);
    }
    if (isset($warning['#link'])) {
      $warning_msg .= ' (' . l(_t('Drupal Docs'), $warning['#link']) . ')';
    }
    $warning = $warning_msg;
  }
  if ($lineno) {
    if ($lineno > 0) {
      $warning = _t('Line @number: !warning [@review_@rule]', array(
        '@number' => $lineno,
        '!warning' => $warning,
        '@review' => $review_name,
        '@rule' => $rule_name,
      ));
    }
    else {
      $warning = _t('File: !warning [@review_@rule]', array(
        '!warning' => $warning,
        '@review' => $review_name,
        '@rule' => $rule_name,
      ));
    }
    if ($line) {
      $warning .= '<pre>' . check_plain($line) . '</pre>';
    }
  }

  // Add informative images and classes to the output.
  $class = 'coder-warning';
  if ($severity_name) {
    $class .= " coder-{$severity_name}";
  }
  $path = _coder_review_path() . '/..';
  $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,
  ));
  $avail_reviews = _coder_review_reviews();
  if (isset($avail_reviews[$review_name]['#image'])) {
    $title = _t('review: @review_@rule', array(
      '@review' => $review_name,
      '@rule' => $rule_name,
    ));
    $review_image = $avail_reviews[$review_name]['#image'];
    $img .= theme('image', array(
      'path' => (substr($review_image, 0, 7) == 'images/' ? "{$path}/" : '') . $review_image,
      'alt' => $title,
      'title' => $title,
      'attributes' => array(
        'align' => 'right',
        'class' => 'coder-review',
      ),
      '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">' . t('Explanation: @description', array(
      '@description' => $description,
    )) . '</div>';
  }
  return '<div class="' . $class . '">' . $img . $warning . '</div>';
}

/**
 * Parses and reads source files into a format for easier review validation.
 *
 * For each source file, the following file lines of code (with trailing
 * newlines) will be added to the Coder arguments array:
 * - #all_array_lines:
 * - #all_lines:
 * - #allphp_array_lines:
 * - #comment_array_lines:
 * - #doublequote_array_lines:
 * - #html_array_lines:
 * - #php_array_lines:
 * - #quote_array_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 source file as an
 * index.
 *
 * @param array $coder_args
 *   A Coder arguments array, passed by reference.
 *
 * @return int
 *   Integer 1 if success.
 */
function _coder_review_read_and_parse_file(array &$coder_args) {

  // Determine the file extension type.
  // Set all allowed PHP extensions to 'php'.
  $pathinfo = pathinfo($coder_args['#filename']);
  $allowed_extensions = array_merge($coder_args['#php_extensions'], $coder_args['#include_extensions'], array(
    'module',
    'theme',
  ));

  // If the file extension is any of the allowed extensions (other than 'js')
  // then set $ext to 'php', otherwise use the actual extension.
  $ext = in_array($pathinfo['extension'], array_diff($allowed_extensions, array(
    'js',
  ))) ? 'php' : $pathinfo['extension'];

  /* The use of variables with 'php' in them ($in_php, $in_all_php, $php_lines,
   * etc.) is misleading. All references to such should be renamed 'code'
   * because we also are using this engine to read 'js' files.
   */

  // Get the path to the module file.
  $filepath = realpath($coder_args['#filename']);
  if (!empty($coder_args['#patch']) || !empty($coder_args['#test']) || file_exists($filepath)) {
    $in_php = $ext == 'js' ? 1 : 0;
    $in_allphp = $in_php;
    $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);
    }
    $content .= "\n";
    $content_length = strlen($content);
    $in_comment = 0;
    $in_quote_html = 0;
    $in_backslash = 0;
    $in_quote = 0;
    $in_heredoc = 0;
    $in_heredoc_length = 0;
    $in_heredoc_html = '';
    $beginning_of_line = 0;
    $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];

      // Look ahead to the next character, to cater for \r\n  line ends.
      $next_char = isset($content[$pos + 1]) ? $content[$pos + 1] : '';
      if ($char == "\n" || $char . $next_char == "\r\n") {

        // End C++ style comments on newline.
        if ($in_comment === '/' || $in_comment === '#') {
          $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 ($quotes) {
            $quote_lines[$lineno] = $quotes;
          }
          $quotes = array();
          foreach ($this_doublequote_lines as $quote_line) {
            if (trim($quote_line, "\r\n") != '') {
              $quotes[] = $quote_line;
            }
          }
          if ($quotes) {
            $doublequote_lines[$lineno] = $quotes;
          }
          if (trim($this_comment_lines, "\r\n") != '') {
            $comment_lines[$lineno] = array(
              $this_comment_lines,
            );
          }
        }

        // Increment $pos by an extra one if the newline was indicated by the
        // two-character CRLF 'carriage return line feed'.
        $pos += $char . $next_char == "\r\n";

        // 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 the
        // characters in the string and replace 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;

          // @note: Trailing char output with starting one.
          unset($char);
        }
        elseif ($in_heredoc) {

          // @note: drupal_substr does not properly handle multi-byte characters in this string.
          // @todo: check other places where the drupal_ string functions fail.
          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 ($ext == 'php' && $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;
          }

          // Do not add comments to php output.
          unset($char);
        }
        else {
          switch ($char) {
            case ',':
            case ')':
            case '(':

            // For 'foo' => 'bar' type syntax.
            case '>':
            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 the previous char is a backslash then we have not found the
              // ending-quote as this one is internal to the string. Keep going.
              if ($pos == 0 || $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 '#':
              $this_comment_lines .= $char;
              $in_comment = $char;
              unset($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.
                $heredoc = '';
                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 = _strlen($in_heredoc);
                    }
                    break;
                  }
                  $this_all_lines .= $char;
                  $heredoc .= $char;
                }

                // 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 ($ext == 'php' && $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;
            }

          // Purposefully fall through.
          default:
            $this_html_lines .= $char;
            break;
        }
      }
    }

    // Add the files lines to the arguments.
    $coder_args['#all_array_lines'] = isset($all_lines) ? $all_lines : array();
    $coder_args['#php_array_lines'] = isset($php_lines) ? $php_lines : array();
    $coder_args['#allphp_array_lines'] = isset($allphp_lines) ? $allphp_lines : array();
    $coder_args['#html_array_lines'] = isset($html_lines) ? $html_lines : array();
    $coder_args['#quote_array_lines'] = isset($quote_lines) ? $quote_lines : array();
    $coder_args['#doublequote_array_lines'] = isset($doublequote_lines) ? $doublequote_lines : array();
    $coder_args['#comment_array_lines'] = isset($comment_lines) ? $comment_lines : array();
    $coder_args['#all_lines'] = isset($full_lines) ? $full_lines : array();
    $coder_args['#raw_contents'] = $content;
    $coder_args['#num_lines'] = isset($full_lines) ? key(array_slice($full_lines, -1, 1, TRUE)) : 0;

    // Given the sanitized PHP lines, determine the class and function for each
    // line.
    $stack = array();
    $class_stack = $class_stack_paren = array();
    $function_stack = $function_stack_paren = array();
    $paren_depth = 0;
    foreach ($coder_args['#php_array_lines'] as $lineno => $line_array) {
      foreach ($line_array as $line) {

        // Check if this line is the beginning of a function definition.
        if (preg_match('/function (\\w+)\\s*\\(/', $line, $match) && !preg_match('/;/', $line)) {
          array_unshift($function_stack, $match[1]);
          array_unshift($function_stack_paren, $paren_depth);
        }

        // Check if this line is the beginning of a class definition.
        if (preg_match('/class (\\w+)/', $line, $match) || preg_match('/interface (\\w+)/', $line, $match)) {
          array_unshift($class_stack, $match[1]);
          array_unshift($class_stack_paren, $paren_depth);
        }

        // Check if this line changes the parenthesis depth.
        if (preg_match_all('/([{}])/', $line, $match)) {
          foreach ($match[0] as $paren_match) {
            $paren_depth += $paren_match == '{' ? 1 : -1;
          }

          // If the depth is now less than then current function depth, pop the
          // function from the stack.
          if ($function_stack_paren && $paren_depth <= $function_stack_paren[0]) {
            array_shift($function_stack);
            array_shift($function_stack_paren);
          }

          // If the depth is now less than the current class depth, pop the
          // class from the stack.
          if ($class_stack_paren && $paren_depth <= $class_stack_paren[0]) {
            array_shift($class_stack);
            array_shift($class_stack_paren);
          }
        }

        // Cache the current function and class for each line of each file.
        $stack[$lineno] = array(
          $class_stack ? $class_stack[0] : '',
          $function_stack ? $function_stack[0] : '',
        );
      }
    }
    $coder_args['#stack'] = $stack;

    // Read the coder warning directives in the comments.
    foreach ($coder_args['#comment_array_lines'] as $lineno => $line_array) {
      foreach ($line_array as $line) {
        $pos = strpos($line, '@ignore ');
        if ($pos !== FALSE && preg_match_all('/([\\w:]+)[\\s,]*/', _substr($line, $pos + 8), $matches)) {
          foreach ($matches[1] as $ignore) {
            list($rule_name, $scope) = explode(':', "{$ignore}:1");
            if ($scope == 'file') {

              // Find the end of the file.
              $scope = $coder_args['#num_lines'] - $lineno;
            }
            elseif ($scope == 'class' || $scope == 'function') {

              // What scope are we looking for?
              // #stack is an array($class_name, $function_name).
              $stack_index = $scope == 'class' ? 0 : 1;

              // Find the current scope.
              $current_scope = NULL;
              foreach (array(
                0,
                1,
              ) as $current_lineno) {
                if (!empty($stack[$lineno + $current_lineno][$stack_index])) {
                  $current_scope = $stack[$lineno + $current_lineno][$stack_index];
                  break;
                }
              }

              // Find the end of the class or function.
              if ($current_scope) {
                for ($scope = 1; !isset($stack[$lineno + $scope]) || $stack[$lineno + $scope][$stack_index] == $current_scope; ++$scope) {
                  if ($lineno + $scope > $coder_args['#num_lines']) {
                    break;
                  }
                }
              }
            }
            elseif ($scope == 'comment') {

              // Find the next line that is not a comment.
              for ($scope = 0; !empty($comment_lines[$lineno + $scope + 1]); ++$scope) {
              }
            }
            if (is_numeric($scope)) {
              for ($line_offset = 0; $line_offset <= $scope; ++$line_offset) {
                $ignores[$lineno + $line_offset][$rule_name] = $rule_name;
              }
            }
          }
        }
      }
    }
    $coder_args['#ignores'] = isset($ignores) && $coder_args['#settings_ignore'] ? $ignores : array();
    return 1;
  }
}

/**
 * Determines the integer severity magic number for a severity string.
 *
 * @param string $severity_name
 *   The name of severity string, e.g., 'minor', 'normal', or 'critical'.
 * @param int $default_value
 *   (optional) An integer magic number to use if severity string is not
 *   recognized. Defaults to SEVERITY_NORMAL.
 *
 * @return int
 *   An 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.
  static $severity_names = array();
  if (!$severity_names) {
    $severity_names = array(
      'minor' => SEVERITY_MINOR,
      'normal' => SEVERITY_NORMAL,
      'critical' => SEVERITY_CRITICAL,
    );
  }
  return isset($severity_names[$severity_name]) ? $severity_names[$severity_name] : $default_value;
}

/**
 * Return string severity for a given error.
 *
 * @param array $coder_args
 *   A Coder settings array. See do_coder_reviews() for format information.
 * @param array $review
 *   Review array that contains rule arrays. See hook_reviews() for details.
 * @param array $rule
 *   Rule array that was triggered. See individual entries from hook_reviews().
 *
 * @return string
 *   A string describing the severity of the error.
 *
 * @see do_coder_reviews()
 * @see hook_review()
 */
function _coder_review_severity_name(array $coder_args, array $review, array $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';
}

/**
 * Performs a code review for a review array.
 *
 * @param array $coder_args
 *   An array of coder_review settings, which must have been prepared with
 *   _coder_review_read_and_parse_file().  See do_coder_reviews() for more
 *   information on the array format.
 * @param array $review
 *   A review array. See hook_review() for format details.
 *
 * @return array
 *   An array of coder_review results. See do_coder_reviews() return value for
 *   more information on the format.
 *
 * @see do_coder_reviews()
 * @see hook_review()
 * @see _coder_review_read_and_parse_file()
 */
function do_coder_review(array $coder_args, array $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',
    'theme',
  ));
  if (!empty($review['#js'])) {
    $allowed_extensions[] = 'js';
  }
  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_name => $rule) {
      $rule += array(
        '#rule_name' => $rule_name,
        '#review_name' => $review['#review_name'],
      );

      // Ignore rules that don't match the file extension.
      $filename_includes = isset($rule['#filename']) ? $rule['#filename'] : (isset($rule['#filename-not']) ? $allowed_extensions : NULL);
      if ($filename_includes) {
        $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']) {

        // #source can be an array, a string, or empty.
        $rule_sources = isset($rule['#source']) ? is_array($rule['#source']) ? $rule['#source'] : array(
          $rule['#source'],
        ) : array(
          'php',
        );
        foreach ($rule_sources as $source) {

          // Permissible values for #source: all, html, comment, allphp or php.
          $source_lines = '#' . $source . '_array_lines';
          $lines = isset($coder_args[$source_lines]) ? $coder_args[$source_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;
            default:
              _message(_t('Invalid rule type @type', array(
                '@type' => $rule['#type'],
              )), 'error');
              break;
          }
        }
      }
    }
  }
  return $results;
}

/**
 * Performs a 'regex' type of coder_review.
 *
 * This type of do_coder_review_* attempts to locate occurances based on a
 * regular expression string.
 *
 * @param array $coder_args
 *   A Coder settings array, passed by reference. See do_coder_review() for format.
 * @param array $review
 *   Review array the current rule belongs to, used by _coder_review_severity_name().
 * @param array $rule
 *   Rule array being checked.
 * @param array $lines
 *   Pertinent source file lines according to rule's '#source' value.
 * @param array $results
 *   Results array variable to save errors to, passed by reference.
 *
 * @see do_coder_review()
 * @see _coder_review_severity_name()
 */
function do_coder_review_regex(array &$coder_args, array $review, array $rule, array $lines, array &$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'] . '/' : '';
    $function_regex = isset($rule['#function']) ? '/' . $rule['#function'] . '/' : '';
    $function_not_regex = isset($rule['#function-not']) ? '/' . $rule['#function-not'] . '/' : '';
    $not_regex = isset($rule['#not']) ? '/' . $rule['#not'] . '/' . $regexflags : '';
    $never_regex = isset($rule['#never']) ? '/' . $rule['#never'] . '/' . $regexflags : '';
    $review_name = $review['#review_name'];
    foreach ($lines as $lineno => $line_array) {
      foreach ($line_array as $line) {
        if (preg_match($regex, $line, $matches)) {

          // Some rules apply only within certain functions and classes.
          list($class_current, $function_current) = isset($coder_args['#stack'][$lineno]) ? $coder_args['#stack'][$lineno] : array(
            '',
            '',
          );
          if ($function_regex && (!$function_current || !preg_match($function_regex, $function_current)) || $function_not_regex && $function_current && preg_match($function_not_regex, $function_current) || $class_regex && (!$class_current || !preg_match($class_regex, $class_current[0])) || $class_not_regex && $class_current && preg_match($class_not_regex, $class_current[0])) {
            continue;
          }

          // 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);
          _coder_review_error($results, $rule, $severity_name, $lineno, $line, $coder_args['#ignores']);
        }
      }
    }
  }
}

/**
 * Builds an error message based on the rule that failed and other information.
 *
 * @param array $results
 *   A Results array variable to save errors to, passed by reference.
 * @param array $rule
 *   A Rule array that triggered the error.
 * @param string $severity_name
 *   A severity of error string as detected by _coder_review_severity_name().
 * @param int $lineno
 *   (optional) The line number on which the error was detected. Defaults to
 *   negative one.
 * @param string $line
 *   (optional) The content of the line that triggered the error. Defaults to an
 *   empty string.
 * @param array $ignores
 *   (optional) Any warnings to ignore. Defaults to an empty array.
 *
 * @see _coder_review_severity_name()
 */
function _coder_review_error(array &$results, array $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 (_coder_review_ignore($rule, $lineno, $ignores)) {
    ++$results['#stats']['ignored'];
  }
  else {
    $key = ($lineno + 1) * 10000 + $_coder_errno++;
    $results[$key] = array(
      'rule' => $rule,
      'severity_name' => $severity_name,
      'review_name' => $rule['#review_name'],
      'rule_name' => $rule['#rule_name'],
      'lineno' => $lineno,
      'line' => $line,
    );
    ++$results['#stats'][$severity_name];
  }
}

/**
 * Determines whether a rule should be ignored.
 *
 * @param array $rule
 *   A Rule array that triggered the error.
 * @param int $lineno
 *   The line number on which the error was detected.
 * @param array $ignores
 *   Any warnings to ignore.
 *
 * @return bool
 *   TRUE if the ruleshould be ignored; otherwise, FALSE.
 */
function _coder_review_ignore(array $rule, $lineno, array $ignores) {
  $name = $rule['#review_name'] . '_' . $rule['#rule_name'];
  return isset($ignores[$lineno][$name]);
}

/**
 * Performs a 'grep' type of coder_review.
 *
 * This type of do_coder_review_* searches for the occurance of a string.
 *
 * @param array $coder_args
 *   A Coder settings array, passed by reference. See do_coder_review() for format.
 * @param array $review
 *   Review array the current rule belongs to, used by _coder_review_severity_name().
 * @param array $rule
 *   Rule array being checked.
 * @param array $lines
 *   Pertinent source file lines according to rule's '#source' value.
 * @param array $results
 *   Results array variable to save errors to, passed by reference.
 *
 * @see do_coder_review()
 * @see _coder_review_severity_name()
 */
function do_coder_review_grep(array &$coder_args, array $review, array $rule, array $lines, array &$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);
          _coder_review_error($results, $rule, $severity_name, $lineno, $line, $coder_args['#ignores']);
        }
      }
    }
  }
}

/**
 * Performs a 'grep_invert' type of coder_review.
 *
 * This type of do_coder_review_* searches for the absence of the occurance of a
 * string.
 *
 * @param array $coder_args
 *   A Coder settings array, passed by reference. See do_coder_review() for format.
 * @param array $review
 *   Review array the current rule belongs to, used by _coder_review_severity_name().
 * @param array $rule
 *   Rule array being checked.
 * @param array $lines
 *   Pertinent source file lines according to rule's '#source' value.
 * @param array $results
 *   Results array variable to save errors to, passed by reference.
 *
 * @see do_coder_review()
 * @see _coder_review_severity_name()
 */
function do_coder_review_grep_invert(array &$coder_args, array $review, array $rule, array $lines, array &$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);
  }
}

/**
 * Performs a 'callback' type of coder_review.
 *
 * This type of do_coder_review_* allows for an arbitrary callback function to
 * perform a review.
 *
 * @param array $coder_args
 *   A Coder settings array, passed by reference. See do_coder_review() for format.
 * @param array $review
 *   Review array the current rule belongs to, used by _coder_review_severity_name().
 * @param array $rule
 *   Rule array being checked.
 * @param array $lines
 *   Pertinent source file lines according to rule's '#source' value.
 * @param array $results
 *   Results array variable to save errors to, passed by reference.
 *
 * @see do_coder_review()
 * @see _coder_review_severity_name()
 */
function do_coder_review_callback(array &$coder_args, array $review, array $rule, array $lines, array &$results) {
  $function = $rule['#value'];
  if ($function) {
    if (function_exists($function)) {
      call_user_func_array($function, array(
        &$coder_args,
        $review,
        $rule,
        $lines,
        &$results,
      ));
    }
    else {
      static $bad;
      if (!isset($bad[$function])) {
        $bad[$function] = 1;
        _message(_t('Invalid rule callback @function', array(
          '@function' => $function,
        )), 'error');
      }
    }
  }
}

/**
 * Searches for the occurance of a string in a line of text.
 *
 * This function uses the fastest available PHP function for searching.
 *
 * @param string $line
 *   Haystack.
 * @param array $rule
 *   A Rule array to process.
 *
 * @return
 *   TRUE if needle is in haystack; otherwise, FALSE.
 */
function _coder_review_search_string($line, $rule) {

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

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

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

/**
 * Returns an 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 string $args
 *   (optional) A string settings argument, can be 'settings', 'active', 'core',
 *   'all' and 'default'. Defaults to 'default'.
 *
 * @return
 *   Associative array of settings in the form of setting name => setting value.
 */
function _coder_review_get_default_settings($arg = 'default') {
  $settings = array(
    'coder_includes' => 0,
    'coder_includes_exclusions' => '',
    'coder_active_modules' => 0,
    'coder_core' => 0,
    'coder_all' => 0,
    'coder_contrib' => 0,
    'coder_files' => 0,
    'coder_file_list' => '',
    'coder_patches' => 0,
    'coder_modules' => array(),
    'coder_themes' => array(),
    'coder_ignore' => 1,
  );
  $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;
}

/**
 * Determines if a module is part of Drupal's core group.
 *
 * @param object $system
 *   ???
 *
 * @return bool
 *   TRUE if a module is part of Drupal's core group; otherwise, FALSE.
 */
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';
}

/**
 * Creates a formatted warning message from a Rule array definition.
 *
 * @param array $rule
 *   A Rule definition array.
 *
 * @return string
 *   A formatted warning message.
 *   @todo Is this an HTML string? Is it sanitized? Needs better explanation.
 */
function _coder_review_warning(array $rule) {

  /* @todo: add version check so we handle tranlations right for older rules definitions.
   * ... or should we just ignore them?
   * This will require passing the review to this function.
   */

  // Call warning callbacks.
  if (isset($rule['#warning_callback'])) {

    // This rule definition is deprecated.
    if (is_callable($rule['#warning_callback']) || function_exists($rule['#warning_callback'])) {
      return $rule['#warning_callback']();
    }
  }
  elseif (isset($rule['#warning'])) {

    // Return array warnings as-is.
    // They get translated in theme_coder_review_warning().
    if (is_array($rule['#warning'])) {
      return $rule['#warning'];
    }

    // Warnings callback functions can now be stored as the #warning element.
    if (is_callable($rule['#warning']) || function_exists($rule['#warning'])) {
      return $rule['#warning']();
    }

    // Translate the warning message and return it.
    return _t($rule['#warning']);
  }

  // No valid warning definition: return a warning about the warning.
  return _t('Unknown warning');
}

/**
 * Define a wrapper t() function that works in Drush cli.
 *
 * @param string $string
 * @param array $args
 * @param array $options
 *
 * @return string
 *
 * @see t()
 */
function _t($string, $args = array(), $options = array()) {
  return _drush() ? dt($string, $args) : t($string, $args, $options);
}

/**
 * Define a wrapper l() function that works in Drush cli.
 *
 * @param string $text
 * @param string $path
 * @param array $options
 *
 * @return string
 *
 * @see l()
 */
function _l($text, $path, $options = array()) {
  return _drush() ? "{$text} {$path}" : l($text, $path, $options);
}

/**
 * Define a wrapper variable_get() function that works in Drush cli.
 *
 * @param string $variable
 *   The name of the variable.
 * @param mixed $default
 *   The default value for the variable.
 *
 * @return mixed
 *   The value of the variable.
 *
 * @see variable_get()
 */
function _variable_get($variable, $default = NULL) {
  return function_exists('variable_get') ? variable_get($variable, $default) : $default;
}

/**
 * Return the valid PHP extensions.
 */
function _coder_review_php_ext() {
  return _variable_get('coder_review_php_ext', array(
    'inc',
    'php',
    'install',
    'test',
  ));
}

/**
 * Determines if currently running with the Drush client.
 *
 * @return bool
 *   TRUE if currently running with the Drush client; otherwise, FALSE.
 */
function _drush() {
  return function_exists('drush_verify_cli') && drush_verify_cli();
}

/**
 * Creates a link to the Drupal node.
 *
 * @param int $nid
 *   The Drupal Node ID to link to.
 * @param string $anchor
 *   (optional) An anchor tag. Defaults to an empty string.
 *
 * @return string
 *   An HTML link to the drupal.org node.
 *   Except when called from Drush, then it is a plain text link.
 */
function _drupalnode($nid, $anchor = '') {
  $link = "http://drupal.org/node/{$nid}";
  if ($anchor) {
    $link .= "#{$anchor}";
  }
  return $link;
}

/**
 * Creates a link to the Drupal API docs.
 *
 * @param string $function
 *   The name of a function to link to.
 * @param string $version
 *   (optional) The Drupal version to link to.
 *
 * @return string
 *   An HTML link to the Drupal API documents for the function and version.
 *   Except when called from Drush, then it is a plain text link.
 */
function _drupalapi($function, $version = '') {
  return _l($function, "http://api.drupal.org/api/function/{$function}/{$version}");
}

/**
 * Creates a link to the PHP API docs.
 *
 * @param string $function
 *   The PHP function documentation to link to.
 *
 * @return string
 *   An HTML link to the PHP documents for the function.
 *   Except when called from Drush, then it is a plain text link.
 */
function _phpapi($function) {
  return _l($function, "http://php.net/{$function}");
}

/**
 * Returns the file system path to the coder_review module.
 *
 * @return string
 *   The file system path to the coder_review module.
 */
function _coder_review_file_path() {
  return dirname(__FILE__);
}

/**
 * Returns the URL path to the coder_review module.
 *
 * @return string
 *   The URL path to the coder_review module.
 */
function _coder_review_path() {
  return drupal_get_path('module', 'coder_review');
}

/**
 * Retrieves data from the cache.
 *
 * @param string $cid
 *   ???
 * @param string $bin
 *   (optional) The name of the bin in which to store the cached data.
 *
 * @return ???
 *   ???
 *
 * @see cache_get()
 */
function _cache_get($cid, $bin = 'default') {
  return function_exists('cache_get') ? cache_get($cid, $bin) : array();
}

/**
 * Stores data in the cache.
 *
 * @param string $cid
 *   ???
 * @param mixed $data
 *   ???
 * @param string $bin
 *   (optional) The name of the bin in which to store the cached data.
 *
 * @return ???
 *   ???
 *
 * @see cache_set()
 */
function _cache_set($cid, $data, $bin = 'default') {
  return function_exists('cache_set') ? cache_set($cid, $data, $bin) : array();
}

/**
 * Returns a sub string.
 *
 * @param string $text
 *   ???
 * @param int $start
 *   ???
 * @param int $length
 *   ???
 *
 * @return ???
 *   ???
 *
 * @see drupal_substr()
 */
function _substr($text, $start, $length = NULL) {
  if (function_exists('drupal_substr')) {
    return drupal_substr($text, $start, $length);
  }
  if ($length === NULL) {
    $length = strlen($text) - $start;
  }
  return substr($text, $start, $length);
}

/**
 * Returns the length of a string.
 *
 * @param string $text
 *   ???
 *
 * @return int
 *   ???
 *
 * @see drupal_strlen()
 */
function _strlen($text) {
  return function_exists('drupal_strlen') ? drupal_strlen($text) : strlen($text);
}

/**
 * Returns a string converted to all UPPERCASE letters.
 *
 * @param string $text
 *   ???
 *
 * @return string
 *   ???
 *
 * @see drupal_strtoupper()
 */
function _strtoupper($text) {
  return function_exists('drupal_strtoupper') ? drupal_strtoupper($text) : strtoupper($text);
}

/**
 * Returns a string converted to all lowercase letters.
 *
 * @param string $text
 *   ???
 *
 * @return string
 *   ???
 *
 * @see drupal_strtolower()
 */
function _strtolower($text) {
  return function_exists('drupal_strtolower') ? drupal_strtolower($text) : strtolower($text);
}

/**
 * Returns all available file paths matching the regular expression.
 *
 * @param ??? $path
 *   ???
 * @param ??? $regex
 *   ???
 * @param bool $recurse
 *   (optional) ??? Defaults to TRUE.
 *
 * @return array
 *   ???
 */
function _file_list($path, $regex, $recurse = TRUE) {
  $files = array();
  foreach (scandir($path) as $file) {
    if ($file != '.' && $file != '..' && is_dir("{$path}/{$file}")) {
      if ($recurse) {
        $files += _file_list("{$path}/{$file}", $regex);
      }
    }
    elseif (preg_match($regex, $file)) {
      $files["{$path}/{$file}"] = "{$path}/{$file}";
    }
  }
  return $files;
}

/**
 * Displays and retrieves messages.
 *
 * @param ??? $message
 *   (optional) ??? Defaults to NULL.
 * @param string $type
 *   (optional) ??? Defaults to 'status'.
 *
 * @return array
 *   ???
 */
function _message($message = NULL, $type = 'status') {
  if (function_exists('drupal_set_message')) {

    // @ignore security_dsm
    drupal_set_message($message);
  }
  static $messages = array();
  if ($message) {
    $messages[$type][] = $message;
  }
  return $messages;
}

Functions

Namesort descending Description
coder_review_reviews Implements hook_reviews().
do_coder_review Performs a code review for a review array.
do_coder_reviews Performs coder reviews for multiple code review definition files.
do_coder_review_callback Performs a 'callback' type of coder_review.
do_coder_review_grep Performs a 'grep' type of coder_review.
do_coder_review_grep_invert Performs a 'grep_invert' type of coder_review.
do_coder_review_regex Performs a 'regex' type of coder_review.
theme_coder_review_warning Returns HTML for a coder_review warning to be included in results.
_cache_get Retrieves data from the cache.
_cache_set Stores data in the cache.
_coder_review_default_reviews Returns a default list of reviews to perform a coder_review analysis upon.
_coder_review_error Builds an error message based on the rule that failed and other information.
_coder_review_file_path Returns the file system path to the coder_review module.
_coder_review_get_default_settings Returns an active settings array for coder_review.
_coder_review_get_reviews_extensions Creates a list of required filename extensions the included in default list.
_coder_review_ignore Determines whether a rule should be ignored.
_coder_review_is_drupal_core Determines if a module is part of Drupal's core group.
_coder_review_modified Determines most recent modification timestamp of key coder_review code files.
_coder_review_path Returns the URL path to the coder_review module.
_coder_review_php_ext Return the valid PHP extensions.
_coder_review_read_and_parse_file Parses and reads source files into a format for easier review validation.
_coder_review_reviews Creates a list of all modules that implement hook_reviews().
_coder_review_search_string Searches for the occurance of a string in a line of text.
_coder_review_severity Determines the integer severity magic number for a severity string.
_coder_review_severity_name Return string severity for a given error.
_coder_review_warning Creates a formatted warning message from a Rule array definition.
_drupalapi Creates a link to the Drupal API docs.
_drupalnode Creates a link to the Drupal node.
_drush Determines if currently running with the Drush client.
_file_list Returns all available file paths matching the regular expression.
_l Define a wrapper l() function that works in Drush cli.
_message Displays and retrieves messages.
_phpapi Creates a link to the PHP API docs.
_strlen Returns the length of a string.
_strtolower Returns a string converted to all lowercase letters.
_strtoupper Returns a string converted to all UPPERCASE letters.
_substr Returns a sub string.
_t Define a wrapper t() function that works in Drush cli.
_variable_get Define a wrapper variable_get() function that works in Drush cli.

Constants

Namesort descending Description
SEVERITY_CRITICAL Specifies the Critical severity level.
SEVERITY_MINOR Specifies the Minor severity level.
SEVERITY_NORMAL Specifies the Normal severity level.