Code

finally understood how to get data back from the session ;-(
[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   // We need to add our own method here, cause PHP doesn't scan 'itself' :-(
79   $user_functions= array('test_defined_functions');
80   
81   // Scan files for used defined functions
82   foreach($array as $file) {
83     // Build array of user defined functions
84     $user_functions= array_merge($user_functions, extract_user_functions($file));
85   }
86   
87   // Scan files for called functions
88   foreach($array as $file) {
89     // Build array of called functions
90     array_push($functions, extract_php_functions($file));
91   }
92   
93   // Make Array 'flat'
94   $functions= array_values_recursive($functions);
96   // remove duplicates
97   $functions= array_unique($functions);
99   // remove 'non-real' functions
100   $reduced_functions= array_filter($functions,"filter_meta_functions");
102   // remove user-defined functions
103   $reduced_functions= array_diff($reduced_functions, $user_functions);
105   // Now the array keys are messed up. Build an array with 'normal' keys (1,2,3,...).
106   $functions= array_values($reduced_functions);
107   
108   return $functions;
111 /**
112  * Scans files in $path an checks for calls of functions that are undefined.
113  * Edit $debug and $verbose to control output. Default is 'false' for both to disallow
114  * console functionality.
115  * @param $path Path to search for files in (default: "./")
116  * @param $pattern RegEx-Pattern for matching PHP-Files (default: "/.+\.(php[34]?$)|(inc$)/")
117  * @param $skip_dirs RegEx-Pattern for directories to ignore (default: ".svn")
118  * @return Array of invalid function-names (Scalars)
119  */
120 function test_functions($path="",$pattern="",$skip_dirs="") {
121   global $debug,$verbose;
123   // Check for empty parameters
124   if(strlen($path)==0) {
125     $path= "./";
126   }
127   
128   if(strlen($pattern)==0) {
129     $pattern= "/.+\.(php[34]?$)|(inc$)/";
130   }
132   if(strlen($skip_dirs)==0) {
133     $skip_dirs= ".svn";
134   }
135   
136   if($debug || $verbose) {
137     print("Scanning Directory...");
138   }
140   // Create file-list
141   $array= read_php_files($path,$pattern,$skip_dirs);
142   if($debug || $verbose) {
143     print("done\n");
144   }
146   $functions= array();
147   // We need to add our own method here, cause PHP doesn't scan 'itself' :-(
148   $user_functions= array('test_functions');
149   $invalid_functions= array();
150   
151   if($debug || $verbose) {
152     print("Importing user functions...");
153   }
154   
155   // Scan files for used defined functions
156   foreach($array as $file) {
157     // Build array of user defined functions
158     $user_functions= array_merge($user_functions, extract_user_functions($file));
159   }
160   
161   if($debug || $verbose) {
162     print("done\n");
163   }
164   
165   if($debug || $verbose) {
166     print("Checking function calls...");
167   }
168   
169   // Scan files for called functions
170   foreach($array as $file) {
171     // Build array of called functions
172     $functions= extract_php_functions($file);
173     foreach($functions as $key => $value) {
174       foreach($value as $fn_key => $function_name) {
175         if(!check_function($function_name) &&
176            // array_search returns key of entry - this can be 0
177            !is_int(array_search($function_name,$user_functions))
178           ) {
179             array_push($invalid_functions, $function_name);
180         }
181       }
182     }
183   }
184   
185   if($debug || $verbose) {
186     print("done\n");
187   }
188   
189   // remove duplicates
190   $invalid_functions= array_unique($invalid_functions);
191   
192   if($debug || $verbose) {
193     foreach($invalid_functions as $function_name) {
194       print("\nFound invalid function ".$function_name);
195     }
196     print("\n");
197   }
199   return $invalid_functions;
202 /**
203  * @return array of content from PHP-Files scanned
204  */
205 function read_php_files($path,$pattern,$skip_dirs) {
206   $result= array();
207   
208   if(is_file($path)) {
209     $file= $path;
211     $php_content= "";
212     
213     // Open Filehandle to process
214     $fh= popen("`which php` -w $file", 'r');
215       while(!feof($fh)) {
216         // Read block-wise
217         $php_content.=fread($fh,1024);
218       }
219     
220     // Close Filehandle
221     pclose($fh);
223     array_push($result, $php_content);
224   } else {
225    $files= list_directory($path,$pattern,$skip_dirs);
227    // Walk through files
228    foreach ($files as $key => $file) {
229      $php_content= "";
230      
231      // Open Filehandle to process
232      $fh= popen("`which php` -w $file", 'r');
233        while(!feof($fh)) {
234          // Read block-wise
235          $php_content.=fread($fh,1024);
236        }
237      
238      // Close Filehandle
239      pclose($fh);
241      array_push($result, $php_content);
242    }
243   }
245   return $result;
248 /** Only used as callback function when doing array_filter().
249  * @return true if $function is not meta-function or keyword, false otherwise
250  */
251 function filter_meta_functions ($function) {
252   $result= true;
253   if(is_meta_function($function)||is_keyword($function)) {
254     $result= false;
255   }
256   return $result;
259 function check_function($function) {
260   $result= false;
261   if(is_keyword($function)) {
262     $result= true;
263   } else if (is_meta_function($function)) {
264     $result= true;
265   } else if (function_exists($function)) {
266     $result= true;
267   }
268   return $result;
271 /** @return true if $function is meta-function, false otherwise */
272 function is_meta_function($function) {
273   $meta_functions= array(
274       "print","array","isset","exit","unset",
275       "die","list","eval","empty"
276     );
277   return(in_array($function,$meta_functions));
280 /** @return true if $param is keyword, false otherwise */
281 function is_keyword($function) {
282   $keywords= array(
283      "if","else","elseif","while","do","case",
284     "for","foreach","break","continue",
285     "switch","declare","return","require",
286     "include","require_once","include_once",
287     "try","catch"
288   );
290     return(in_array($function,$keywords));
293 function extract_php_functions($string) {
294   // Function names have to be A-z or _ for the first letter
295   // plus 0-9, +, - for the following letters
296   // Length is 2 minimum
297   $rx_name="[A-Za-z_][A-Za-z0-9_+-]+";
299   // Function calls can be after ',', '.', '(' or spaces
300   // Note: Method calls are not detected - this is wanted
301   $rx_function="[.,(\s]\s*($rx_name)\s*\(";
303   // Get code inside <?php ... (question-mark)>
304   $string= trim(array_pop(get_php_code($string)));
305   $string= remove_strings($string);
306   $result= array();
307   preg_match_all("/$rx_function/",$string,$result);
308   array_shift($result);
311   // We need to check if "function" is actual a class created by new operator
312   // negative lookbehind isn't possible with php yet
313   $classes= array();
314   foreach($result[0] as $key => $function) {
315     $match= array();
316     if(preg_match("/new\s+($function)/",$string,$match)) {
317       array_shift($match);
318       array_push($classes,$match);
319     }
320   }
322   // We need to manually unset the keys that we found above
323   foreach($classes as $key => $value) {
324     for($i=0;$i<count($value);$i++) {
325       if(isset($result[0])) {
326         unset($result[0][array_search($value[$i],$result[0])]);
327       }
328     }
329   }
331   return $result;
334 /**
335  * Extracts function-calls in php-code
336  * @param $string php-code to extract function calls from
337  * @return array of functions called
338  */
339 function extract_user_functions($string) {
340   $rx_name="[A-Za-z_][A-Za-z0-9_+-]*";
341   $rx_user_function="function\s+($rx_name)\s*\(";
343   // Get code inside <?php ... (question-mark)>
344   $string= trim(array_pop(get_php_code($string)));
345   $string= remove_strings($string);
346   $result= array();
347   preg_match_all("/$rx_user_function/s",$string,$result);
348   array_shift($result);
349   
350   return $result= array_pop($result);
353 /**
354  * @return php-code without <?php ... markers
355  */
356 function get_php_code($string) {
357   $array= array();
358   preg_match_all('/\<(\?php|\%)(.*)(\?|\%)\>/sx', $string, $array);
360   // Pop the first two entries as we don't want them
361   $match= array_shift($array);
362   $match= array_shift($array);
364   // Do the same for the last entry
365   $match= array_pop($array);
367   // The array $result only contains regex group (.*) now
368   return array_pop($array);
371 /**
372  * Returns 'print ();' for 'print ("hello world!\n");'
373  * AND:
374  * Returns '$message= sprintf(_()...); for
375  *  $message= sprintf(_("Command '%s', specified as POSTREMOVE for plugin '%s' doesn't seem to exist.")...);
376  * Note the "doesn't"
377  * @param $string code with strings
378  * @return code with strings removed
379  */
380 function remove_strings($string) {
381   $result= "";
382   
383   $inside_string=false;
384   $inside_string_uni=false;
385   $inside_string_double=false;
386   
387   // Walk through $string
388   for($i=0;$i<strlen($string);$i++) {
389     if($string[$i]=="'" && ($i>0 && ($string[$i-1] != "\\"))) {
390       if(!$inside_string_uni) {
391         // We're now inside
392         if(!$inside_string_double) {
393           $inside_string_uni= true;
394           $inside_string= true;
395         }
396       } else {
397         // We're now outside
398         $inside_string_uni= false;
399         if(!$inside_string_double) {
400           $inside_string= false;
401         }
402       }
403     } else if($string[$i]=='"' && ($i>0 && ($string[$i-1] != "\\"))) {
404       if(!$inside_string_double) {
405         // We're now inside
406         if(!$inside_string_uni) {
407           $inside_string_double= true;
408           $inside_string= true;
409         }
410       } else {
411         // We're now outside
412         $inside_string_double= false;
413         if(!$inside_string_uni) {
414           $inside_string= false;
415         }
416       }
417     } else {
418       // Push char to $result if not inside string
419       $result.= (!$inside_string)?$string[$i]:"";
420     }
421   }
423   // Return string
424   return $result;
427 /** 
428  * Iteratively scans directory $dir for files (filenames) matching regular expression $pattern
429  * @param $dir Initial Directory to start scan
430  * @param $pattern Regex-Pattern to match on files to scan
431  * @param $skip_dirs Regex-Patten to match on directories to skip
432  * @return file list
433  */
434 function list_directory($dir, $pattern, $skip_dirs) {
435   $file_list= '';
436   $stack[]= $dir;
437   while ($stack) {
438     $current_dir= array_pop($stack);
439     if ($dh= opendir($current_dir)) {
440       while (($file= readdir($dh)) !== false) {
441          if ($file !== '.' && $file !== '..' && preg_match("/$skip_dirs/",$file)==0) {
442           $current_file= "{$current_dir}/{$file}";
443           if (is_file($current_file) && preg_match($pattern, $current_file)) {
444             $file_list[]= "{$current_dir}/{$file}";
445           } elseif (is_dir($current_file)) {
446             $stack[]= $current_file;
447           }
448         }
449       }
450     }
451   }
452   return $file_list;
455 function array_values_recursive($array) {
456   $arrayValues = array();
457   
458   foreach ($array as $value) {
459     if (is_scalar($value) OR is_resource($value)) {
460       $arrayValues[] = $value;
461     } elseif (is_array($value)) {
462       $arrayValues = array_merge($arrayValues, array_values_recursive($value));
463     }
464   }
466   return $arrayValues;
469 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
470 ?>