MediaWiki REL1_30
checkSyntax.php
Go to the documentation of this file.
1<?php
24require_once __DIR__ . '/Maintenance.php';
25
31class CheckSyntax extends Maintenance {
32
33 // List of files we're going to check
34 private $mFiles = [], $mFailures = [], $mWarnings = [];
36
37 public function __construct() {
38 parent::__construct();
39 $this->addDescription( 'Check syntax for all PHP files in MediaWiki' );
40 $this->addOption( 'with-extensions', 'Also recurse the extensions folder' );
41 $this->addOption(
42 'path',
43 'Specific path (file or directory) to check, either with absolute path or '
44 . 'relative to the root of this MediaWiki installation',
45 false,
46 true
47 );
48 $this->addOption(
49 'list-file',
50 'Text file containing list of files or directories to check',
51 false,
52 true
53 );
54 $this->addOption(
55 'modified',
56 'Check only files that were modified (requires Git command-line client)'
57 );
58 $this->addOption( 'syntax-only', 'Check for syntax validity only, skip code style warnings' );
59 }
60
61 public function getDbType() {
63 }
64
65 public function execute() {
66 $this->buildFileList();
67
68 $this->output( "Checking syntax (using php -l, this can take a long time)\n" );
69 foreach ( $this->mFiles as $f ) {
70 $this->checkFileWithCli( $f );
71 if ( !$this->hasOption( 'syntax-only' ) ) {
72 $this->checkForMistakes( $f );
73 }
74 }
75 $this->output( "\nDone! " . count( $this->mFiles ) . " files checked, " .
76 count( $this->mFailures ) . " failures and " . count( $this->mWarnings ) .
77 " warnings found\n" );
78 }
79
83 private function buildFileList() {
84 global $IP;
85
86 $this->mIgnorePaths = [
87 ];
88
89 $this->mNoStyleCheckPaths = [
90 // Third-party code we don't care about
91 "/activemq_stomp/",
92 "EmailPage/PHPMailer",
93 "FCKeditor/fckeditor/",
94 '\bphplot-',
95 "/svggraph/",
96 "\bjsmin.php$",
97 "PEAR/File_Ogg/",
98 "QPoll/Excel/",
99 "/geshi/",
100 "/smarty/",
101 ];
102
103 if ( $this->hasOption( 'path' ) ) {
104 $path = $this->getOption( 'path' );
105 if ( !$this->addPath( $path ) ) {
106 $this->error( "Error: can't find file or directory $path\n", true );
107 }
108
109 return; // process only this path
110 } elseif ( $this->hasOption( 'list-file' ) ) {
111 $file = $this->getOption( 'list-file' );
112 MediaWiki\suppressWarnings();
113 $f = fopen( $file, 'r' );
114 MediaWiki\restoreWarnings();
115 if ( !$f ) {
116 $this->error( "Can't open file $file\n", true );
117 }
118 $path = trim( fgets( $f ) );
119 while ( $path ) {
120 $this->addPath( $path );
121 }
122 fclose( $f );
123
124 return;
125 } elseif ( $this->hasOption( 'modified' ) ) {
126 $this->output( "Retrieving list from Git... " );
127 $files = $this->getGitModifiedFiles( $IP );
128 $this->output( "done\n" );
129 foreach ( $files as $file ) {
130 if ( $this->isSuitableFile( $file ) && !is_dir( $file ) ) {
131 $this->mFiles[] = $file;
132 }
133 }
134
135 return;
136 }
137
138 $this->output( 'Building file list...', 'listfiles' );
139
140 // Only check files in these directories.
141 // Don't just put $IP, because the recursive dir thingie goes into all subdirs
142 $dirs = [
143 $IP . '/includes',
144 $IP . '/mw-config',
145 $IP . '/languages',
146 $IP . '/maintenance',
147 $IP . '/skins',
148 ];
149 if ( $this->hasOption( 'with-extensions' ) ) {
150 $dirs[] = $IP . '/extensions';
151 }
152
153 foreach ( $dirs as $d ) {
154 $this->addDirectoryContent( $d );
155 }
156
157 // Manually add two user-editable files that are usually sources of problems
158 if ( file_exists( "$IP/LocalSettings.php" ) ) {
159 $this->mFiles[] = "$IP/LocalSettings.php";
160 }
161
162 $this->output( 'done.', 'listfiles' );
163 }
164
170 private function getGitModifiedFiles( $path ) {
171 global $wgMaxShellMemory;
172
173 if ( !is_dir( "$path/.git" ) ) {
174 $this->error( "Error: Not a Git repository!\n", true );
175 }
176
177 // git diff eats memory.
178 $oldMaxShellMemory = $wgMaxShellMemory;
179 if ( $wgMaxShellMemory < 1024000 ) {
180 $wgMaxShellMemory = 1024000;
181 }
182
183 $ePath = wfEscapeShellArg( $path );
184
185 // Find an ancestor in common with master (rather than just using its HEAD)
186 // to prevent files only modified there from showing up in the list.
187 $cmd = "cd $ePath && git merge-base master HEAD";
188 $retval = 0;
189 $output = wfShellExec( $cmd, $retval );
190 if ( $retval !== 0 ) {
191 $this->error( "Error retrieving base SHA1 from Git!\n", true );
192 }
193
194 // Find files in the working tree that changed since then.
195 $eBase = wfEscapeShellArg( rtrim( $output, "\n" ) );
196 $cmd = "cd $ePath && git diff --name-only --diff-filter AM $eBase";
197 $retval = 0;
198 $output = wfShellExec( $cmd, $retval );
199 if ( $retval !== 0 ) {
200 $this->error( "Error retrieving list from Git!\n", true );
201 }
202
203 $wgMaxShellMemory = $oldMaxShellMemory;
204
205 $arr = [];
206 $filename = strtok( $output, "\n" );
207 while ( $filename !== false ) {
208 if ( $filename !== '' ) {
209 $arr[] = "$path/$filename";
210 }
211 $filename = strtok( "\n" );
212 }
213
214 return $arr;
215 }
216
222 private function isSuitableFile( $file ) {
223 $file = str_replace( '\\', '/', $file );
224 $ext = pathinfo( $file, PATHINFO_EXTENSION );
225 if ( $ext != 'php' && $ext != 'inc' && $ext != 'php5' ) {
226 return false;
227 }
228 foreach ( $this->mIgnorePaths as $regex ) {
229 $m = [];
230 if ( preg_match( "~{$regex}~", $file, $m ) ) {
231 return false;
232 }
233 }
234
235 return true;
236 }
237
243 private function addPath( $path ) {
244 global $IP;
245
246 return $this->addFileOrDir( $path ) || $this->addFileOrDir( "$IP/$path" );
247 }
248
254 private function addFileOrDir( $path ) {
255 if ( is_dir( $path ) ) {
256 $this->addDirectoryContent( $path );
257 } elseif ( file_exists( $path ) ) {
258 $this->mFiles[] = $path;
259 } else {
260 return false;
261 }
262
263 return true;
264 }
265
271 private function addDirectoryContent( $dir ) {
272 $iterator = new RecursiveIteratorIterator(
273 new RecursiveDirectoryIterator( $dir ),
274 RecursiveIteratorIterator::SELF_FIRST
275 );
276 foreach ( $iterator as $file ) {
277 if ( $this->isSuitableFile( $file->getRealPath() ) ) {
278 $this->mFiles[] = $file->getRealPath();
279 }
280 }
281 }
282
288 private function checkFileWithCli( $file ) {
289 $res = exec( 'php -l ' . wfEscapeShellArg( $file ) );
290 if ( strpos( $res, 'No syntax errors detected' ) === false ) {
291 $this->mFailures[$file] = $res;
292 $this->output( $res . "\n" );
293
294 return false;
295 }
296
297 return true;
298 }
299
306 private function checkForMistakes( $file ) {
307 foreach ( $this->mNoStyleCheckPaths as $regex ) {
308 $m = [];
309 if ( preg_match( "~{$regex}~", $file, $m ) ) {
310 return;
311 }
312 }
313
314 $text = file_get_contents( $file );
315 $tokens = token_get_all( $text );
316
317 $this->checkEvilToken( $file, $tokens, '@', 'Error supression operator (@)' );
318 $this->checkRegex( $file, $text, '/^[\s\r\n]+<\?/', 'leading whitespace' );
319 $this->checkRegex( $file, $text, '/\?>[\s\r\n]*$/', 'trailing ?>' );
320 $this->checkRegex( $file, $text, '/^[\xFF\xFE\xEF]/', 'byte-order mark' );
321 }
322
323 private function checkRegex( $file, $text, $regex, $desc ) {
324 if ( !preg_match( $regex, $text ) ) {
325 return;
326 }
327
328 if ( !isset( $this->mWarnings[$file] ) ) {
329 $this->mWarnings[$file] = [];
330 }
331 $this->mWarnings[$file][] = $desc;
332 $this->output( "Warning in file $file: $desc found.\n" );
333 }
334
335 private function checkEvilToken( $file, $tokens, $evilToken, $desc ) {
336 if ( !in_array( $evilToken, $tokens ) ) {
337 return;
338 }
339
340 if ( !isset( $this->mWarnings[$file] ) ) {
341 $this->mWarnings[$file] = [];
342 }
343 $this->mWarnings[$file][] = $desc;
344 $this->output( "Warning in file $file: $desc found.\n" );
345 }
346}
347
348$maintClass = "CheckSyntax";
349require_once RUN_MAINTENANCE_IF_MAIN;
$dir
Definition Autoload.php:8
$wgMaxShellMemory
Maximum amount of virtual memory available to shell processes under linux, in KB.
wfShellExec( $cmd, &$retval=null, $environ=[], $limits=[], $options=[])
Execute a shell command, with time and memory limits mirrored from the PHP configuration if supported...
wfEscapeShellArg()
Version of escapeshellarg() that works better on Windows.
$maintClass
Maintenance script to check syntax of all PHP files in MediaWiki.
getGitModifiedFiles( $path)
Returns a list of tracked files in a Git work tree differing from the master branch.
addFileOrDir( $path)
Add given file to file list, or, if it's a directory, add its content.
execute()
Do the actual work.
isSuitableFile( $file)
Returns true if $file is of a type we can check.
checkFileWithCli( $file)
Check a file for syntax errors using php -l.
__construct()
Default constructor.
addPath( $path)
Add given path to file list, searching it in include path if needed.
checkEvilToken( $file, $tokens, $evilToken, $desc)
addDirectoryContent( $dir)
Add all suitable files in given directory or its subdirectories to the file list.
buildFileList()
Build the list of files we'll check for syntax errors.
getDbType()
Does the script need different DB access? By default, we give Maintenance scripts normal rights to th...
checkForMistakes( $file)
Check a file for non-fatal coding errors, such as byte-order marks in the beginning or pointless ?...
checkRegex( $file, $text, $regex, $desc)
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
const DB_NONE
Constants for DB access type.
hasOption( $name)
Checks to see if a particular param exists.
addDescription( $text)
Set the description text.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
getOption( $name, $default=null)
Get an option, or return the default.
$res
Definition database.txt:21
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling output() to send it all. It could be easily changed to send incrementally if that becomes useful
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account incomplete not yet checked for validity & $retval
Definition hooks.txt:266
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title after the basic globals have been set but before ordinary actions take place $output
Definition hooks.txt:2225
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults error
Definition hooks.txt:2581
$IP
Definition update.php:3
require_once RUN_MAINTENANCE_IF_MAIN
$tokens