array(), 'output' => new OutputModule() ); $stderr = fopen( 'php://stderr', 'w' ); $config = array( 'svn' => false, 'modules' => array(), 'output_format' => 'text', 'output_file' => 'php://stdout', 'quiet' => false, ); $help = "Usage: {$argv[0]} [options] file|path [file|path ...] {$argv[0]} [options] -b base_path -r revision[,revision ...] Options: -b path Set the base path for the scan. Useful if you want --base-path path to scan individual files in a code base that don't live in the project's base directory. -f format Select the output format. Defaults to text. --format format -h Display this usage information --help -m modulename Loads the requested scanning module. If this --module modulename parameter is not specified, all available modules will be loaded. -o filename Write output to a file instead of stdout --output filename -q Suppresses all progress output --quiet -r Get files from a comma separated list of SVN revisions. You must supply a base path first for this! --svn Enables SVN integration "; $faults = array(); class ScannerModule { var $faults; var $blame; var $file; function ScannerModule() { $this->faults = array(); $this->blame = array(); err( "Initializing " . get_class( $this ) . "...\n" ); } function fault( $object, $level, $reason = '' ) { global $config, $revisions, $faults; if( $this->file['revision'] > 0 && $this->blame[$object['line']]['revision'] != $this->file['revision'] ) { /* If files have been added using SVN revisions, filter out any faulty changes that aren't a part of the requested changeset(s). */ return false; } $object['file'] = filename( $object['file'] ); $faults[] = $this->faults[] = array( 'module' => get_class( $this ), 'object' => $object, 'file' => $this->file, 'level' => $level, 'reason' => $reason, 'svn' => ( $config['svn'] === true ) ? $this->blame[$object['line']] : '' ); //var_dump( $faults ); die(); } function parserCallback( $object ) { } function preScan( $file ) { global $config, $svn_root, $svn_base; $this->file = $file; if( $config['svn'] === true ) { $this->blame = array(); $output = array(); if( $file['revision'] > 0 ) { exec( "svn blame -r {$file['revision']} {$svn_root}{$svn_base}/{$file['filename']} 2>/dev/null", $output, $result ); } else { exec( "svn blame '{$file['filename']}' 2>/dev/null", $output, $result ); } if( $result == 0 ) { foreach( $output as $line => $text ) { $matches = array(); preg_match( '/^\s*(\d+)\s+([^\s]+)/', $text, $matches ); $this->blame[$line + 1] = array( 'author' => $matches[2], 'revision' => $matches[1] ); } } } } function postScan( $filename ) { } } class OutputModule { function display() { $this->write( 'php://output' ); } function write( $filename ) { } } function _callback( $object ) { global $modules, $files, $current_file; $object['file'] = $files[$current_file - 1]['filename']; foreach( $modules['scanner'] as $module ) { $module->parserCallback( $object ); } } function addModule( $module_instance ) { global $modules; if( $module_instance instanceof ScannerModule ) { $modules['scanner'][] = $module_instance; } elseif( $module_instance instanceof OutputModule ) { $modules['output'] = $module_instance; } } function filename( $filename ) { global $base_path; $filename = realpath( $filename ); if( strpos( $filename, $base_path ) === 0 ) { $filename = substr( $filename, strlen( $base_path ) ); } return $filename; } function err( $string ) { global $stderr, $config; if( $config['quiet'] === false ) { fputs( $stderr, $string ); } } // Handle application arguments $revisions = array(); $files = array(); $base_path = false; for( $i = 1; $i < $argc; $i++ ) { switch( $argv[$i] ) { case '-b': case '--base-path': $new_base = $argv[++$i]; if( is_dir( $new_base ) ) { $base_path = realpath( $new_base ) . '/'; } break; case '-f': case '--format': $config['output_format'] = $argv[++$i]; break; case '-h': case '--help': die( $help ); break; case '-m': case '--module': $config['modules'][] = $argv[++$i]; break; case '-o': case '--output': $config['output_file'] = $argv[++$i]; break; case '-q': case '--quiet': $config['quiet'] = true; break; case '-r': $revs = explode( ',', $argv[++$i] ); if( $base_path === false ) { die( "Set a base path before supplying SVN revisions\n" ); } // First, find out what the full path is so we can trim it down to the relative one. $xml = shell_exec( "svn info --xml $base_path" ); $parser = xml_parser_create(); xml_parser_set_option( $parser, XML_OPTION_SKIP_WHITE, 1 ); xml_parse_into_struct( $parser, $xml, $values, $index ); xml_parser_free( $parser ); foreach( $values as $value ) { switch( $value['tag'] ) { case 'URL': $svn_url = $value['value']; break; case 'ROOT': $svn_root = $value['value']; } } $svn_base = substr( $svn_url, strlen( $svn_root ) ); foreach( $revs as $rev ) { $revisions[] = $rev = intval( $rev ); $xml = shell_exec( "svn log -v --xml -r $rev $base_path 2>/dev/null" ); $parser = xml_parser_create(); xml_parser_set_option( $parser, XML_OPTION_SKIP_WHITE, 1 ); xml_parse_into_struct( $parser, $xml, $values, $index ); xml_parser_free( $parser ); foreach( $values as $value ) { if( $value['tag'] == 'PATH' && isset( $value['attributes']['ACTION'] ) && in_array( $value['attributes']['ACTION'], array( 'A', 'M' ) ) && strtolower( substr( $value['value'], -4 ) ) == '.php' ) { $file = substr( $value['value'], strlen( $svn_base ) + 1 ); $files[] = array( 'filename' => $file, 'revision' => $rev, 'contents' => shell_exec( "svn cat -r {$rev} {$svn_root}{$value['value']} 2>/dev/null" ) ); } } } case '--svn': $config['svn'] = true; break; default: if( file_exists( $argv[$i] ) && strtolower( substr( $argv[$i], -4 ) ) == '.php' ) { $base_path = ( $base_path === false ) ? realpath( dirname( $argv[$i] ) ) . '/' : $base_path; $files[] = array( 'filename' => $argv[$i], 'revision' => 0 ); } else if( is_dir( $argv[$i] ) ) { $base_path = ( $base_path === false ) ? realpath( $argv[$i] ) . '/' : $base_path; exec( "find {$argv[$i]} -iname '*.php' 2>/dev/null", $output, $result ); foreach( $output as $file ) { $files[] = array( 'filename' => $file, 'revision' => 0 ); } } } } if( count( $files ) == 0 ) { if( count( $revisions ) > 0 ) { die( "Revisions invalid or contained no files from the supplied path\n" ); } else { die( $help ); } } // Dig into the modules folder and load up what we find $module_files = scandir( 'modules' ); foreach( $module_files as $module_file ) { if( strtolower( substr( $module_file, -4 ) ) == '.php' ) { $module = substr( $module_file, 0, strlen( $module_file ) - 4 ); list( $type, $module ) = split( '_', $module ); switch( $type ) { case 'output': if( $module == $config['output_format'] ) { require_once( "modules/{$module_file}" ); } break; case 'scanner': if( count( $config['modules'] ) == 0 || in_array( $module, $config['modules'] ) ) { require_once( "modules/{$module_file}" ); } break; } } } $parser = new PHPParser(); $parser->registerCallback( '_callback' ); err( "Parsing files...\n" ); $current_file = 0; $total = count( $files ); $lastpct = 0; foreach( $files as $file ) { $current_file++; if( $current_file == 1 ) { err( 0 ); } else { $pct = intval( $current_file / $total * 100 ); if( $pct != $lastpct && $pct % 2 == 0 ) { err( $pct % 10 == 0 ? $pct : '.' ); $lastpct = $pct; } } foreach( $modules['scanner'] as $module ) { $module->preScan( $file ); } $file_contents = ( $file['revision'] > 0 ? shell_exec( "svn cat -r {$rev} {$svn_root}{$svn_base}/{$file['filename']} 2>/dev/null" ) : file_get_contents( $file['filename'] ) ); $parser->parse( $file_contents ); } err( "\n" ); $modules['output']->write( $config['output_file'] ); sleep( 1 ); err( sprintf( "Found %d faults in %d files.\n", count( $faults ), count( $files ) ) ); ?>