Code

Added ability to build array of used functions.
[gosa.git] / include / functions_test.inc
1 <?
2 /*
3  * This code is part of GOsa (https://gosa.gonicus.de)
4  * Copyright (C) 2003 Cajus Pollmeier
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20  
21 /* This file builds a list of used php_functions in all files matching $pattern
22  * (walks recursively through current directory but skips dirs matching $skip_dirs).
23  *
24  * 1. builds an array of filenames
25  * 2. get contents from files
26  * 2. fetches user defined functions using content
27  * 3. fetches functions calls using content
28  * 4. checks function calls for matching known functions
29  */
31 /** 
32  * Print status messages if set to true (useful for command-line use).
33  * default: false */
34 $verbose= false;
36 /** 
37  * Print debug messages if set to true.
38  * default: false 
39  */
40 $debug= false;
42 /**
43  * Used by command-line client.
44  */
45 function cmd_test_functions($path) {
46   global $verbose;
47   $verbose= true;
48   test_functions($path);
49 }
51 /**
52  * Scans files in $path an checks for calls of functions.
53  * @param $path Path to search for files in (default: "./")
54  * @param $pattern RegEx-Pattern for matching PHP-Files (default: "/.+\.(php[34]?$)|(inc$)/")
55  * @param $skip_dirs RegEx-Pattern for directories to ignore (default: ".svn")
56  * @return Array of used function-names
57  */
58 function test_defined_functions($path="",$pattern="",$skip_dirs="") {
59   global $debug,$verbose;
61   // Check for empty parameters
62   if(strlen($path)==0) {
63     $path= "./";
64   }
65   
66   if(strlen($pattern)==0) {
67     $pattern= "/.+\.(php[34]?$)|(inc$)/";
68   }
70   if(strlen($skip_dirs)==0) {
71     $skip_dirs= ".svn";
72   }
73   
74   // Create file-list
75   $array= read_php_files($path,$pattern,$skip_dirs);
77   $functions= array();
78   $user_functions= array();
79   
80   // Scan files for used defined functions
81   foreach($array as $file) {
82     // Build array of user defined functions
83     $user_functions= array_merge($user_functions, extract_user_functions($file));
84   }
85   
86   // Scan files for called functions
87   foreach($array as $file) {
88     // Build array of called functions
89     array_push($functions, extract_php_functions($file));
90   }
91   
92   // Make Array 'flat'
93   $functions= array_values_recursive($functions);
95   // remove duplicates
96   $functions= array_unique($functions);
98   // remove 'non-real' functions
99   $reduced_functions= array_filter($functions,"filter_meta_functions");
101   // remove user-defined functions
102   $reduced_functions= array_diff($reduced_functions, $user_functions);
104   // Now the array keys are messed up. Build an array with 'normal' keys (1,2,3,...).
105   $functions= array_values($reduced_functions);
106   
107   return $functions;
110 /**
111  * Scans files in $path an checks for calls of functions that are undefined.
112  * Edit $debug and $verbose to control output. Default is 'false' for both to disallow
113  * console functionality.
114  * @param $path Path to search for files in (default: "./")
115  * @param $pattern RegEx-Pattern for matching PHP-Files (default: "/.+\.(php[34]?$)|(inc$)/")
116  * @param $skip_dirs RegEx-Pattern for directories to ignore (default: ".svn")
117  * @return Array of invalid function-names (Scalars)
118  */
119 function test_functions($path="",$pattern="",$skip_dirs="") {
120   global $debug,$verbose;
122   // Check for empty parameters
123   if(strlen($path)==0) {
124     $path= "./";
125   }
126   
127   if(strlen($pattern)==0) {
128     $pattern= "/.+\.(php[34]?$)|(inc$)/";
129   }
131   if(strlen($skip_dirs)==0) {
132     $skip_dirs= ".svn";
133   }
134   
135   if($debug || $verbose) {
136     print("Scanning Directory...");
137   }
139   // Create file-list
140   $array= read_php_files($path,$pattern,$skip_dirs);
141   if($debug || $verbose) {
142     print("done\n");
143   }
145   $functions= array();
146   $user_functions= array();
147   $invalid_functions= array();
148   
149   if($debug || $verbose) {
150     print("Importing user functions...");
151   }
152   
153   // Scan files for used defined functions
154   foreach($array as $file) {
155     // Build array of user defined functions
156     $user_functions= array_merge($user_functions, extract_user_functions($file));
157   }
158   
159   if($debug || $verbose) {
160     print("done\n");
161   }
162   
163   if($debug || $verbose) {
164     print("Checking function calls...");
165   }
166   
167   // Scan files for called functions
168   foreach($array as $file) {
169     // Build array of called functions
170     $functions= extract_php_functions($file);
171     foreach($functions as $key => $value) {
172       foreach($value as $fn_key => $function_name) {
173         if(!check_function($function_name) &&
174            // array_search returns key of entry - this can be 0
175            !is_int(array_search($function_name,$user_functions))
176           ) {
177             array_push($invalid_functions, $function_name);
178         }
179       }
180     }
181   }
182   
183   if($debug || $verbose) {
184     print("done\n");
185   }
186   
187   // remove duplicates
188   $invalid_functions= array_unique($invalid_functions);
189   
190   if($debug || $verbose) {
191     foreach($invalid_functions as $function_name) {
192       print("\nFound invalid function ".$function_name);
193     }
194     print("\n");
195   }
197   return $invalid_functions;
200 /**
201  * @see
202  * @return array of content from PHP-Files scanned
203  */
204 function read_php_files($path,$pattern,$skip_dirs) {
205   $result= array();
206   
207   if(is_file($path)) {
208     $file= $path;
210     $php_content= "";
211     
212     // Open Filehandle to process
213     $fh= popen("`which php` -w $file", 'r');
214       while(!feof($fh)) {
215         // Read block-wise
216         $php_content.=fread($fh,1024);
217       }
218     
219     // Close Filehandle
220     pclose($fh);
222     array_push($result, $php_content);
223   } else {
224    $files= list_directory($path,$pattern,$skip_dirs);
226    // Walk through files
227    foreach ($files as $key => $file) {
228      $php_content= "";
229      
230      // Open Filehandle to process
231      $fh= popen("`which php` -w $file", 'r');
232        while(!feof($fh)) {
233          // Read block-wise
234          $php_content.=fread($fh,1024);
235        }
236      
237      // Close Filehandle
238      pclose($fh);
240      array_push($result, $php_content);
241    }
242   }
244   return $result;
247 /** Only used as callback function when doing array_filter().
248  * @return true if $function is not meta-function or keyword, false otherwise
249  */
250 function filter_meta_functions ($function) {
251   $result= true;
252   if(is_meta_function($function)||is_keyword($function)) {
253     $result= false;
254   }
255   return $result;
258 function check_function($function) {
259   $result= false;
260   if(is_keyword($function)) {
261     $result= true;
262   } else if (is_meta_function($function)) {
263     $result= true;
264   } else if (function_exists($function)) {
265     $result= true;
266   }
267   return $result;
270 /** @return true if $function is meta-function, false otherwise */
271 function is_meta_function($function) {
272   $meta_functions= array(
273       "print","array","isset","exit","unset",
274       "die","list","eval","empty"
275     );
276   return(in_array($function,$meta_functions));
279 /** @return true if $param is keyword, false otherwise */
280 function is_keyword($function) {
281   $keywords= array(
282      "if","else","elseif","while","do","case",
283     "for","foreach","break","continue",
284     "switch","declare","return","require",
285     "include","require_once","include_once",
286     "try","catch"
287   );
289     return(in_array($function,$keywords));
292 function extract_php_functions($string) {
293   // Function names have to be A-z or _ for the first letter
294   // plus 0-9, +, - for the following letters
295   // Length is 2 minimum
296   $rx_name="[A-Za-z_][A-Za-z0-9_+-]+";
298   // Function calls can be after ',', '.', '(' or spaces
299   // Note: Method calls are not detected - this is wanted
300   $rx_function="[.,(\s]\s*($rx_name)\s*\(";
302   // Get code inside <?php ... (question-mark)>
303   $string= trim(array_pop(get_php_code($string)));
304   $string= remove_strings($string);
305   $result= array();
306   preg_match_all("/$rx_function/",$string,$result);
307   array_shift($result);
310   // We need to check if "function" is actual a class created by new operator
311   // negative lookbehind isn't possible with php yet
312   $classes= array();
313   foreach($result[0] as $key => $function) {
314     $match= array();
315     if(preg_match("/new\s+($function)/",$string,$match)) {
316       array_shift($match);
317       array_push($classes,$match);
318     }
319   }
321   // We need to manually unset the keys that we found above
322   foreach($classes as $key => $value) {
323     for($i=0;$i<count($value);$i++) {
324       if(isset($result[0])) {
325         unset($result[0][array_search($value[$i],$result[0])]);
326       }
327     }
328   }
330   return $result;
333 /**
334  * Extracts function-calls in php-code
335  * @param $string php-code to extract function calls from
336  * @return array of functions called
337  */
338 function extract_user_functions($string) {
339   $rx_name="[A-Za-z_][A-Za-z0-9_+-]*";
340   $rx_user_function="function\s+($rx_name)\s*\(";
342   // Get code inside <?php ... (question-mark)>
343   $string= trim(array_pop(get_php_code($string)));
344   $string= remove_strings($string);
345   $result= array();
346   preg_match_all("/$rx_user_function/s",$string,$result);
347   array_shift($result);
348   
349   return $result= array_pop($result);
352 /**
353  * @return php-code without <?php ... markers
354  */
355 function get_php_code($string) {
356   $array= array();
357   preg_match_all('/\<(\?php|\%)(.*)(\?|\%)\>/sx', $string, $array);
359   // Pop the first two entries as we don't want them
360   $match= array_shift($array);
361   $match= array_shift($array);
363   // Do the same for the last entry
364   $match= array_pop($array);
366   // The array $result only contains regex group (.*) now
367   return array_pop($array);
370 /**
371  * Returns 'print ();' for 'print ("hello world!\n");'
372  * AND:
373  * Returns '$message= sprintf(_()...); for
374  *  $message= sprintf(_("Command '%s', specified as POSTREMOVE for plugin '%s' doesn't seem to exist.")...);
375  * Note the "doesn't"
376  * @param $string code with strings
377  * @return code with strings removed
378  */
379 function remove_strings($string) {
380   $result= "";
381   
382   $inside_string=false;
383   $inside_string_uni=false;
384   $inside_string_double=false;
385   
386   // Walk through $string
387   for($i=0;$i<strlen($string);$i++) {
388     if($string[$i]=="'" && ($i>0 && ($string[$i-1] != "\\"))) {
389       if(!$inside_string_uni) {
390         // We're now inside
391         if(!$inside_string_double) {
392           $inside_string_uni= true;
393           $inside_string= true;
394         }
395       } else {
396         // We're now outside
397         $inside_string_uni= false;
398         if(!$inside_string_double) {
399           $inside_string= false;
400         }
401       }
402     } else if($string[$i]=='"' && ($i>0 && ($string[$i-1] != "\\"))) {
403       if(!$inside_string_double) {
404         // We're now inside
405         if(!$inside_string_uni) {
406           $inside_string_double= true;
407           $inside_string= true;
408         }
409       } else {
410         // We're now outside
411         $inside_string_double= false;
412         if(!$inside_string_uni) {
413           $inside_string= false;
414         }
415       }
416     } else {
417       // Push char to $result if not inside string
418       $result.= (!$inside_string)?$string[$i]:"";
419     }
420   }
422   // Return string
423   return $result;
426 /** 
427  * Iteratively scans directory $dir for files (filenames) matching regular expression $pattern
428  * @param $dir Initial Directory to start scan
429  * @param $pattern Regex-Pattern to match on files to scan
430  * @param $skip_dirs Regex-Patten to match on directories to skip
431  * @return file list
432  */
433 function list_directory($dir, $pattern, $skip_dirs) {
434   $file_list= '';
435   $stack[]= $dir;
436   while ($stack) {
437     $current_dir= array_pop($stack);
438     if ($dh= opendir($current_dir)) {
439       while (($file= readdir($dh)) !== false) {
440          if ($file !== '.' && $file !== '..' && preg_match("/$skip_dirs/",$file)==0) {
441           $current_file= "{$current_dir}/{$file}";
442           if (is_file($current_file) && preg_match($pattern, $current_file)) {
443             $file_list[]= "{$current_dir}/{$file}";
444           } elseif (is_dir($current_file)) {
445             $stack[]= $current_file;
446           }
447         }
448       }
449     }
450   }
451   return $file_list;
454 function array_values_recursive($array) {
455   $arrayValues = array();
456   
457   foreach ($array as $value) {
458     if (is_scalar($value) OR is_resource($value)) {
459       $arrayValues[] = $value;
460     } elseif (is_array($value)) {
461       $arrayValues = array_merge($arrayValues, array_values_recursive($value));
462     }
463   }
465   return $arrayValues;
468 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
469 ?>