filename);
$_coder_coders[] = $file->name;
}
}
/**
* Get all of the code review modules
*/
function _coder_reviews() {
$reviews = array();
// get the review definitions from the include directory
global $_coder_coders;
if ($_coder_coders) {
foreach ($_coder_coders as $coder) {
$function = $coder .'_reviews';
if (function_exists($function)) {
if ($review = call_user_func($function)) {
$reviews = array_merge($reviews, $review);
}
}
}
}
// get the contributed module review definitions
if ($review = module_invoke_all('reviews')) {
$reviews = array_merge($reviews, $review);
}
return $reviews;
}
/**
* Implementation of hook_cron().
*/
function coder_cron() {
if ($use_cache = variable_get('coder_cache', 1)) {
// TODO: move some of the work here... is this really worth it?
}
}
/**
* Implementation of hook_perm().
*/
function coder_perm() {
return array('view code review');
}
/**
* Implementation of hook_menu().
*/
function coder_menu($may_cache) {
$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/core',
'title' => t('Core'),
'callback' => 'coder_page',
'access' => user_access('view code review'),
'type' => MENU_NORMAL_ITEM,
);
$items[] = array(
'path' => 'coder/active',
'title' => t('Active'),
'callback' => 'coder_page',
'access' => user_access('view code review'),
'type' => MENU_NORMAL_ITEM,
);
$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().
*/
function coder_form_alter($form_id, &$form) {
if ($form_id == 'system_modules') {
if (user_access('view code review')) {
foreach ($form['name'] as $name => $data) {
$form['name'][$name]['#value'] = l($data['#value'], "coder/$name");
}
}
}
}
/**
* Helper functions for settings form
*/
function _coder_default_reviews() {
return drupal_map_assoc(array('style', 'security'));
}
function _coder_settings_form($settings, &$modules, &$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
$reviews = _coder_reviews();
foreach ($reviews as $name => $review) {
$review_options[$name] = l($review['#title'], $review['#link']);
}
// what review standards should be applied
$form['coder_reviews_group'] = array(
'#type' => 'fieldset',
'#title' => t('Reviews'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['coder_reviews_group']['coder_reviews'] = array(
'#type' => 'checkboxes',
'#options' => $review_options,
'#description' => t('apply the checked coding reviews'),
'#default_value' => $settings['coder_reviews'],
);
$form['coder_reviews_group']['coder_severity'] = array(
'#type' => 'radios',
'#options' => array(
1 => 'minor (most)',
5 => 'normal',
9 => 'critical (fewest)'
),
'#description' => t('show warnings at or above the severity warning level'),
'#default_value' => $settings['coder_severity'],
);
// get the modules
$sql = "SELECT name, filename, status FROM {system} WHERE type = 'module' ORDER BY weight ASC, filename ASC";
$result = db_query($sql);
while ($module = db_fetch_object($result)) {
$display_name = $module->name;
if ($module->status) {
$display_name .= t(' (active)');
$active_modules[$module->name] = $module->name;
}
if (_coder_is_drupal_core_module($module)) {
$display_name .= t(' (core)');
$core_modules[$module->name] = $module->name;
}
$all_modules[$module->name] = l($display_name, "coder/$module->name");
$files[$module->name] = $module->filename;
}
// display active modules option
$form['coder_modules_group'] = array(
'#type' => 'fieldset',
'#title' => t('Modules'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['coder_modules_group']['coder_active_modules'] = array(
'#type' => 'checkbox',
'#default_value' => $settings['coder_active_modules'],
'#title' => t('code review all active modules'),
);
$form['coder_modules_group']['coder_core_modules'] = array(
'#type' => 'checkbox',
'#default_value' => $settings['coder_core_modules'],
'#title' => t('code review core files (php, modules, and includes)'),
);
$form['coder_modules_group']['coder_includes'] = array(
'#type' => 'checkbox',
'#default_value' => $settings['coder_includes'],
'#title' => t('review all include files (.inc and .php files)'),
);
if (arg(0) == 'admin') {
$form['coder_modules_group']['coder_cache'] = array(
'#type' => 'checkbox',
'#default_value' => $settings['coder_cache'],
'#title' => t('use the experimental coder cache'),
);
}
// display the modules in a fieldset
$form['coder_modules_group']['coder_modules_subgroup'] = array(
'#type' => 'fieldset',
'#title' => t('Select Specific Modules'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
if ($settings['coder_active_modules']) {
if ($settings['coder_core_modules']) {
$modules = array_intersect($active_modules, $core_modules);
}
else {
$modules = $active_modules;
}
}
elseif ($settings['coder_core_modules']) {
$modules = $core_modules;
}
elseif (!is_array($settings['coder_modules'])) {
$modules = $active_modules;
}
else {
$modules = $settings['coder_modules'];
}
foreach ($all_modules as $name => $checkbox) {
$classes = array();
if (in_array($name, $active_modules)) {
$classes[] = "coder-active";
}
if (in_array($name, $core_modules)) {
$classes[] = "coder-core";
}
$form['coder_modules_group']['coder_modules_subgroup']["coder_modules-$name"] = array(
'#type' => 'checkbox',
'#title' => $checkbox,
'#default_value' => isset($modules[$name]),
'#attributes' => array('class' => implode(' ', $classes)),
);
}
return $form;
}
/**
* Implementation of settings page for Drupal 5
*/
function coder_admin_settings() {
$settings = _coder_get_default_settings();
$form = _coder_settings_form($settings, $modules, $files);
$form['#submit']['coder_settings_form_submit'] = array();
$form['#submit']['system_settings_form_submit'] = array();
return system_settings_form($form);
}
function coder_settings_form_submit($form_id, &$form_values) {
variable_set('coder_modules', _coder_settings_modules($form_values));
}
function _coder_settings_modules(&$form_values) {
$modules = array();
foreach ($form_values as $key => $value) {
if (substr($key, 0, 14) == 'coder_modules-') {
if ($value == 1) {
$module = substr($key, 14);
$modules[$module] = 1;
}
unset($form_values[$key]);
}
}
return $modules;
}
function coder_page_form_submit($form_id, $form_values) {
// HELP: is there a better way to get these to coder_page_form()???
return FALSE;
}
/**
* Implementation of code review page
*/
function coder_page() {
return drupal_get_form('coder_page_form');
}
function _coder_get_default_settings($args = '') {
$settings['coder_reviews'] = variable_get('coder_reviews', _coder_default_reviews());
$settings['coder_severity'] = variable_get('coder_severity', 5);
$settings['coder_cache'] = variable_get('coder_cache', 1);
// determine any options based on the passed in URL,
switch ($args) {
case '':
$settings['coder_active_modules'] = variable_get('coder_active_modules', 1);
$settings['coder_core_modules'] = variable_get('coder_core_modules', 0);
$settings['coder_includes'] = variable_get('coder_includes', 0);
$settings['coder_modules'] = variable_get('coder_modules', '');
break;
case 'active':
$settings['coder_active_modules'] = 1;
break;
case 'core':
$settings['coder_core_modules'] = 1;
$settings['coder_includes'] = 1;
break;
default:
$settings['coder_includes'] = 1;
$settings['coder_modules'] = array($args => $args);
break;
}
return $settings;
}
function coder_page_form() {
// HELP: is there a better way to get these from coder_page_form_submit()???
$form_values = $_POST;
if (isset($form_values['op'])) {
$settings = $form_values;
$settings['coder_modules'] = _coder_settings_modules($form_values);
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, $modules, $files)) {
// 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 ($settings['coder_core_modules']) {
$coder_args = array(
'#reviews' => $reviews,
'#severity' => $settings['coder_severity'],
'#filename' => $filename,
);
$form['core_php'] = array(
'#type' => 'fieldset',
'#title' => 'core (php)',
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#weight' => ++ $module_weight,
);
$phpfiles = file_scan_directory('.', '.*\.php', array('.', '..', 'CVS'), 0, FALSE, 'name', 0);
_coder_page_form_includes($form, $coder_args, 'core_php', $phpfiles, 2);
$form['core_includes'] = array(
'#type' => 'fieldset',
'#title' => 'core (includes)',
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#weight' => ++ $module_weight,
);
$includefiles = drupal_system_listing('.*\.inc$', 'includes', 'name', 0);
_coder_page_form_includes($form, $coder_args, 'core_includes', $includefiles, 0);
}
// loop through the selected modules, preparing the code review results
$dups = array(); // used to avoid duplicate includes
foreach ($modules as $name => $checked) {
if ($checked) {
// process this one file
$filename = $files[$name];
if (!$filename) {
drupal_set_message(t('Code Review is only available on module files (%module.module not found)', array('%module' => $name)));
continue;
}
$coder_args = array(
'#reviews' => $reviews,
'#severity' => $settings['coder_severity'],
'#name' => $name,
'#filename' => $filename,
);
$results = do_coder_reviews($coder_args);
// output the results in a collapsible fieldset
$form[$name] = array(
'#type' => 'fieldset',
'#title' => $filename,
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#weight' => ++ $module_weight,
);
if (count($results) == 0) {
$results[] = t('No Problems Found');
}
else {
$form[$name]['#collapsed'] = FALSE;
}
$form[$name]['output'] = array(
'#value' => theme('coder', $name, $filename, $results),
'#weight' => -1,
);
// process the same directory include files
if ($settings['coder_includes']) {
// NOTE: convert to the realpath here so drupal_system_listing
// doesn't return additional paths (i.e., try "module").
$path = str_replace('\\', '/', dirname(realpath($filename)));
$offset = strpos($path, dirname($filename));
if (!isset($dups[$path])) {
$dups[$path] = 1;
$includefiles = drupal_system_listing('.*\.(inc|php)$', $path, 'name', 0);
_coder_page_form_includes($form, $coder_args, $name, $includefiles, $offset);
}
}
}
}
// prepend any output with the list of code reviews performed
if (!$form) {
$form = array('#value' => t('No modules found'));
}
// prepend the settings form
$form['settings'] = array(
'#type' => 'fieldset',
'#title' => t('Settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#weight' => -1,
'#prefix' => t('
Expand the Settings below to select options for this code review, or change the
default settings for all code reviews.
', array('@default' => url('admin/settings/coder'))),
);
$form['settings'][] = $coder_form;
$form['settings']['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
}
return $form;
}
function _coder_page_form_includes(&$form, $coder_args, $name, $files, $offset) {
foreach ($files as $file) {
$filename = drupal_substr($file->filename, $offset);
$coder_args['#filename'] = $filename;
$results = do_coder_reviews($coder_args);
// output the results in a collapsible fieldset
$form[$name][$filename] = array(
'#type' => 'fieldset',
'#title' => $filename,
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#weight' => ++ $weight,
);
if (count($results) == 0) {
$results[] = t('No Problems Found');
}
else {
$form[$name][$filename]['#collapsed'] = FALSE;
$form[$name]['#collapsed'] = FALSE;
}
$form[$name][$filename]['output'] = array(
'#value' => theme('coder', $name, $filename, $results),
);
}
}
function _coder_modified() {
static $_coder_mtime;
if (!isset($_coder_mtime)) {
$path = drupal_get_path('module', 'coder');
$includefiles = drupal_system_listing('.*\.(inc|module)$', $path, 'name', 0);
$_coder_mtime = 0;
foreach ($includefiles as $file) {
$mtime = filemtime(realpath($file->filename));
if ($mtime > $_coder_mtime) {
$_coder_mtime = $mtime;
}
}
}
return $_coder_mtime;
}
function do_coder_reviews($coder_args) {
// the cache is still experimental, so users must enable it
if ($use_cache = variable_get('coder_cache', 1)) {
// cache the results because:
$cache_key = 'coder:'. implode(':', array_keys($coder_args['#reviews'])) . $coder_args['#severity'] .':'. $coder_args['#filename'];
$cache_mtime = filemtime(realpath($coder_args['#filename']));
if ($cache_serialized_results = cache_get($cache_key)) {
$cache_results = unserialize($cache_serialized_results->data);
if ($cache_results['mtime'] == $cache_mtime && _coder_modified() < $cache_serialized_results->created) {
return $cache_results['results'];
}
}
}
$results = array();
// skip php include files when the user requested severity is above minor
if (drupal_substr($coder_args['#filename'], -4) == '.php') {
if ($coder_args['#severity'] > 1) {
return $results;
}
}
// read the file
if (_coder_read_and_parse_file($coder_args)) {
// do all of the code reviews
foreach ($coder_args['#reviews'] as $review) {
if ($result = do_coder_review($coder_args, $review)) {
$results += $result;
}
}
// 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;
}
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);
$content_length = drupal_strlen($content);
// parse the file:
// - strip comments
// - strip quote content // - strip stuff not in php // - break into lines
$lineno = 0;
for ($pos = 0; $pos < $content_length; $pos ++) {
// get the current character
$char = $content[$pos];
if ($char == "\n") {
if ($in_comment == '/') { // end C++ style comments on newline
unset($in_comment);
}
// assume that html inside quotes doesn't span newlines
unset($in_quote_html);
// remove blank lines now, so we avoid processing them over-and-over
if (trim($all_lines[$lineno]) == '') {
unset($all_lines[$lineno]);
}
if (trim($php_lines[$lineno]) == '') {
unset($php_lines[$lineno]);
}
if (trim($html_lines[$lineno]) == '') {
unset($html_lines[$lineno]);
}
$lineno ++;
$beginning_of_line = 1;
continue;
}
$all_lines[$lineno] .= $char;
if ($in_php) {
// look for the ending php tag which tags precedence over everything
if ($char == '?' && $content[$pos + 1] == '>') {
unset($char);
unset($in_php);
$all_lines[$lineno] .= '>';
$pos ++;
}
// when in a quoted string, look for the trailing quote
// strip characters in the string, replacing with '' or ""
elseif ($in_quote) {
if ($in_backslash) {
unset($in_backslash);
}
elseif ($char == '\\') {
$in_backslash = '\\';
}
elseif ($char == $in_quote && !$in_backslash) {
unset($in_quote);
}
elseif ($char == '<') {
$in_quote_html = '>';
}
if ($in_quote && $in_quote_html) {
$html_lines[$lineno] .= $char;
}
if ($char == $in_quote_html) {
unset($in_quote_html);
}
unset($char); // NOTE: trailing char output with starting one
}
elseif ($in_heredoc) {
if ($beginning_of_line && $char == $in_heredoc[0] && substr($content, $pos, $in_heredoc_length) == $in_heredoc) {
$all_lines[$lineno] .= substr($content, $pos + 1, $in_heredoc_length - 1);
unset($in_heredoc);
$pos += $in_heredoc_length;
}
elseif ($char == '<') {
$in_heredoc_html = '>';
}
if ($in_heredoc && $in_heredoc_html) {
$html_lines[$lineno] .= $char;
}
if ($char == $in_heredoc_html) {
unset($in_heredoc_html);
}
unset($char);
}
// when in a comment look for the trailing comment
elseif ($in_comment) {
if ($in_comment == '*' && $char == '*' && $content[$pos + 1] == '/') {
unset($in_comment);
$all_lines[$lineno] .= '/';
$pos ++;
}
unset($char); // don't add comments to php output
}
else {
switch ($char) {
case '\'':
case '"':
if ($content[$pos - 1] != '\\') {
$php_lines[$lineno] .= $char;
$in_quote = $char;
}
break;
case '/':
$next_char = $content[$pos + 1];
if ($next_char == '/' || $next_char == '*') {
unset($char);
$in_comment = $next_char;
$all_lines[$lineno] .= $next_char;
$pos ++;
}
break;
case '<':
if ($content[$pos + 1] == '<' && $content[$pos + 2] == '<') {
unset($char);
$all_lines[$lineno] .= '<<';
// get the heredoc word
// read until the end-of-line
for ($pos += 3; $pos < $content_length; $pos ++) {
$char = $content[$pos];
if ($char == "\n") {
$pos --;
if (preg_match('/^\s+(\w+)/', $heredoc, $match)) {
$in_heredoc = $match[1];
$in_heredoc_length = drupal_strlen($in_heredoc);
}
break;
}
$all_lines[$lineno] .= $char;
$heredoc .= $char;
}
unset($heredoc);
// replace heredoc's with an empty string
$php_lines[$lineno] .= "''";
unset($char);
}
break;
}
}
if (isset($char)) {
$php_lines[$lineno] .= $char;
}
}
else {
switch ($char) {
case '<':
if ($content[$pos + 1] == '?') {
if ($content[$pos + 2] == ' ') {
$in_php = 1;
$all_lines[$lineno] .= '? ';
$pos += 2;
}
elseif (substr($content, $pos + 2, 3) == 'php') {
$in_php = 1;
$all_lines[$lineno] .= '?php';
$pos += 4;
}
break;
}
// FALTHROUGH
default:
$html_lines[$lineno] .= $char;
break;
}
}
}
// add the files lines to the arguments
$coder_args['#all_lines'] = $all_lines;
$coder_args['#php_lines'] = $php_lines;
$coder_args['#html_lines'] = $html_lines;
return 1;
}
}
function _coder_severity($severity_name, $default_value = 5) {
// NOTE: implemented this way in hopes that it is faster than a php switch
if (!isset($severity_names)) {
$severity_names = array(
'minor' => 1,
'normal' => 5,
'critical' => 9,
);
}
if (isset($severity_names[$severity_name])) {
return $severity_names[$severity_name];
}
return $default_value;
}
function _coder_severity_name($coder_args, $review, $rule) {
// NOTE: warnings in php includes are suspicious because
// php includes are frequently 3rd party products
if (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';
}
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($rule['#severity'], $default_severity);
if ($severity >= $coder_args['#severity']) {
if (isset($rule['#original'])) { // deprecated
$lines = $coder_args['#all_lines'];
}
elseif (isset($rule['#source'])) { // all, html, comment, or php
$source = '#'. $rule['#source'] .'_lines';
$lines = $coder_args[$source];
}
else {
$lines = $coder_args['#php_lines'];
}
if ($lines) {
switch ($rule['#type']) {
case 'regex':
do_coder_review_regex($coder_args, $review, $rule, $lines, $results);
break;
case 'grep':
do_coder_review_grep($coder_args, $review, $rule, $lines, $results);
break;
case 'callback':
do_coder_review_callback($coder_args, $review, $rule, $lines, $results);
break;
}
}
}
}
}
return $results;
}
function do_coder_review_regex(&$coder_args, $review, $rule, $lines, &$results) {
if ($regex = $rule['#value']) {
$regex = '/'. $regex .'/i';
foreach ($lines as $lineno => $line) {
if (preg_match($regex, $line, $matches)) {
// don't match some regex's
if ($not = $rule['#not']) {
foreach ($matches as $match) {
if (preg_match('/'. $not .'/i', $match)) {
continue 2;
}
}
}
$line = $coder_args['#all_lines'][$lineno];
$severity_name = _coder_severity_name($coder_args, $review, $rule);
_coder_error($results, $rule, $severity_name, $lineno, $line);
}
}
}
}
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 report 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);
}
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);
}
function do_coder_review_grep(&$coder_args, $review, $rule, $lines, &$results) {
if ($regex = $rule['#value']) {
$regex = '/'. $regex .'/i';
foreach ($lines as $lineno => $line) {
if (preg_match($regex, $line)) {
return;
}
}
$severity_name = _coder_severity_name($coder_args, $review, $rule);
_coder_error($results, $rule, $severity_name);
}
}
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));
}
}
}
function _coder_is_drupal_core_module($module) {
static $core;
if (!isset($core)) {
$core = array(
'aggregator' => 1,
'archive' => 1,
'block' => 1,
'blog' => 1,
'blogapi' => 1,
'book' => 1,
'color' => 1,
'comment' => 1,
'contact' => 1,
'drupal' => 1,
'filter' => 1,
'forum' => 1,
'help' => 1,
'legacy' => 1,
'locale' => 1,
'menu' => 1,
'node' => 1,
'page' => 1,
'path' => 1,
'ping' => 1,
'poll' => 1,
'profile' => 1,
'search' => 1,
'statistics' => 1,
'system' => 1,
'taxonomy' => 1,
'throttle' => 1,
'tracker' => 1,
'upload' => 1,
'user' => 1,
'watchdog' => 1,
);
}
return $core[$module->name];
}
/**
* Theming functions below...
*/
function theme_coder($name, $filename, $results) {
$output = ''. basename($filename) .'
';
if (count($results)) {
$output .= theme('item_list', $results);
}
$output .= '';
return $output;
}
function theme_coder_warning($warning, $severity_name, $lineno = 0, $line = '') {
if ($lineno) {
$warning = t('Line @number: !warning', array('@number' => $lineno, '!warning' => $warning));
if ($line) {
$warning .= ''. check_plain($line) .'
';
}
}
$class = "coder-warning";
if ($severity_name) {
$class .= " coder-$severity_name";
}
$path = drupal_get_path('module', 'coder');
$img = theme('image', $path ."/images/$severity_name.png", t('severity: @severity', array('@severity' => $severity_name)), '', array('align' => 'right', 'class' => 'coder'), FALSE);
return ''. $img . $warning .'
';
}
function theme_drupalapi($function, $version = 'HEAD') {
return l($function, "http://api.drupal.org/api/$version/function/$function");
}