Code

Updated translation, fixed some typo/errors
[gosa.git] / include / functions_test.inc
1 <?php
2 /*
3  * This code is part of GOsa (https://gosa.gonicus.de)
4  * Copyright (C) 2005 Jan Wenzel
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 and 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;
60   $verbose= true;
62   // Check for empty parameters. If empty, set default.
63   if(strlen($path)==0) {
64     $path= "./";
65   }
66   if(strlen($pattern)==0) {
67     $pattern= "/.+\.(php[34]?$)|(inc$)/";
68   }
69   if(strlen($skip_dirs)==0) {
70     $skip_dirs= ".svn";
71   }
72   
73   // Create file-list
74   $array= read_php_files($path,$pattern,$skip_dirs);
76   // Needed arrays
77   $functions= array();
78   
79   // We need to include our own function here, because PHP seems to lock the current executed function
80   $user_functions= array('test_defined_functions');
81   
82   // Scan files for used defined functions
83   foreach($array as $file) {
84     // Build array of user defined functions
85     $user_functions= array_merge($user_functions, extract_user_functions($file));
86   }
87   
88   // Scan files for called functions
89   foreach($array as $file) {
90     // Build array of called functions
91     array_push($functions, extract_php_functions($file));
92   }
93   
94   // Make Array 'flat'
95   $functions= array_values_recursive($functions);
97   // remove duplicates
98   $functions= array_unique($functions);
100   // remove 'non-real' functions
101   $reduced_functions= array_filter($functions,"filter_meta_functions");
103   // remove user-defined functions
104   $reduced_functions= array_diff($reduced_functions, $user_functions);
106   // Now the array keys are messed up. Build an array with 'normal' keys (1,2,3,...).
107   $functions= array_values($reduced_functions);
108   
109   return $functions;
112 /**
113  * Scans files in $path and checks for calls of functions that are undefined.
114  * Edit $debug and $verbose to control output. Default is 'false' for both to disallow
115  * console functionality.
116  * @param $path Path to search for files in (default: "./")
117  * @param $pattern RegEx-Pattern for matching PHP-Files (default: "/.+\.(php[34]?$)|(inc$)/")
118  * @param $skip_dirs RegEx-Pattern for directories to ignore (default: ".svn")
119  * @return Array of invalid function-names (Scalars)
120  */
121 function test_functions($path="",$pattern="",$skip_dirs="") {
122   global $debug,$verbose;
124   // Check for empty parameters
125   if(strlen($path)==0) {
126     $path= "./";
127   }
128   
129   if(strlen($pattern)==0) {
130     $pattern= "/.+\.(php[34]?$)|(inc$)/";
131   }
133   if(strlen($skip_dirs)==0) {
134     $skip_dirs= ".svn";
135   }
136   
137   if($debug || $verbose) {
138     print("Scanning Directory...");
139   }
141   // Create file-list
142   $array= read_php_files($path,$pattern,$skip_dirs);
143   if($debug || $verbose) {
144     print("done\n");
145   }
147   $functions= array();
148   $user_functions= array();
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  * Reads $path for files matching $pattern but not in $skip_dirs.
204  * @param $path Path to search for files in (default: "./")
205  * @param $pattern RegEx-Pattern for matching PHP-Files (default: "/.+\.(php[34]?$)|(inc$)/")
206  * @param $skip_dirs RegEx-Pattern for directories to ignore (default: ".svn")
207  * @return array of content from PHP-Files scanned
208  */
209 function read_php_files($path,$pattern,$skip_dirs) {
210   global $debug,$verbose;
211   $result= array();
212   
213   if(is_file($path)) {
214     $file= $path;
215     
216     $php_content="";
217     
218     // Open Filehandle to process
219     $fh= popen("`which php` -w $file", 'r');
220       while(!feof($fh)) {
221         // Read block-wise
222         $php_content.=fread($fh,1024);
223       }
224     
225     // Close Filehandle
226     pclose($fh);
228     array_push($result, $php_content);
229   } else {
230    $files= list_directory($path,$pattern,$skip_dirs);
232    // Walk through files
233    foreach ($files as $key => $file) {
234      $php_content= "";
235      
236      // Open Filehandle to process
237      $fh= popen("`which php` -w $file", 'r');
238        while(!feof($fh)) {
239          // Read block-wise
240          $php_content.=fread($fh,1024);
241        }
242      
243      // Close Filehandle
244      pclose($fh);
246      array_push($result, $php_content);
247    }
248   }
250   return $result;
253 /** Only used as callback function when doing array_filter().
254  * @return true if $function is not meta-function or keyword, false otherwise
255  */
256 function filter_meta_functions ($function) {
257   $result= true;
258   if(is_meta_function($function)||is_keyword($function)) {
259     $result= false;
260   }
261   return $result;
264 /**
265  * Returns true if $function is a known php-function, false otherwise.
266  * @return true if $function is a known php-function, false otherwise.
267  */
268 function check_function($function) {
269   $result= false;
270   if(is_keyword($function)) {
271     $result= true;
272   } else if (is_meta_function($function)) {
273     $result= true;
274   } else if (function_exists($function)) {
275     $result= true;
276   }
277   return $result;
280 /** Returns true if $function is meta-function, false otherwise.
281  * @return true if $function is meta-function, false otherwise.
282  */
283 function is_meta_function($function) {
284   $meta_functions= array(
285       "print","array","isset","exit","unset",
286       "die","list","eval","empty"
287     );
288   return(in_array($function,$meta_functions));
291 /** Returns true if $function is keyword, false otherwise.
292  * @return true if $function is keyword, false otherwise.
293  */
294 function is_keyword($function) {
295   $keywords= array(
296      "if","else","elseif","while","do","case",
297     "for","foreach","break","continue",
298     "switch","declare","return","require",
299     "include","require_once","include_once",
300     "try","catch"
301   );
303   return(in_array($function,$keywords));
306 /**
307  * Returns array of called functions.
308  * @param $string PHP-Code
309  * @return array of called functions.
310  */
311 function extract_php_functions($string) {
312   // Function names have to be A-z or _ for the first letter
313   // plus 0-9, +, - for the following letters
314   // Length is 2 minimum
315   $rx_name="[A-Za-z_][A-Za-z0-9_+-]+";
317   // Function calls can be after ',', '.', '(' or spaces
318   // Note: Method calls are not detected - this is wanted
319   $rx_function="[.,(\s]\s*($rx_name)\s*\(";
321   // Get code inside <?php ... (question-mark)>
322   $string= trim(array_pop(get_php_code($string)));
323   $string= remove_strings($string);
324   $result= array();
325   preg_match_all("/$rx_function/",$string,$result);
326   array_shift($result);
329   // We need to check if "function" is actual a class created by new operator,
330   // but negative lookbehind isn't possible with php yet.
331   // So we must scan the array again and remove the found keys later (not while we're walking through).
332   $classes= array();
333   foreach($result[0] as $key => $function) {
334     $match= array();
335     if(preg_match("/new\s+($function)/",$string,$match)) {
336       array_shift($match);
337       array_push($classes,$match);
338     }
339   }
341   // We need to manually unset the keys that we found above
342   foreach($classes as $key => $value) {
343     for($i=0;$i<count($value);$i++) {
344       if(isset($result[0])) {
345         unset($result[0][array_search($value[$i],$result[0])]);
346       }
347     }
348   }
350   return $result;
353 /**
354  * Extracts function-definitions from php-code
355  * @param $string php-code to extract function definitions from
356  * @return array of functions defined
357  */
358 function extract_user_functions($string) {
359   $rx_name="[A-Za-z_][A-Za-z0-9_+-]*";
360   $rx_user_function="function\s+($rx_name)\s*\(";
362   // Get code inside <?php ... (question-mark)>
363   $string= trim(array_pop(get_php_code($string)));
364   $string= remove_strings($string);
365   $result= array();
366   preg_match_all("/$rx_user_function/s",$string,$result);
367   array_shift($result);
368   
369   return $result= array_pop($result);
372 /**
373  * Returns php-code without <?php ... markers.
374  * @return php-code without <?php ... markers.
375  */
376 function get_php_code($string) {
377   $array= array();
378   preg_match_all('/\<(\?php|\%)(.*)(\?|\%)\>/sx', $string, $array);
380   // Pop the first two entries as we don't want them
381   $match= array_shift($array);
382   $match= array_shift($array);
384   // Do the same for the last entry
385   $match= array_pop($array);
387   // The array $result only contains regex group (.*) now
388   return array_pop($array);
391 /**
392  * Removes all double and single quotes strings from sourcecode.
393  * Returns 'print ();' for 'print ("hello world!\n");'
394  * AND:
395  * Returns '$message= sprintf(_()$foo,$bar); for
396  *  $message= sprintf(_("Command '%s', specified as POSTREMOVE for plugin '%s' doesn't seem to exist.")$foo,$bar);
397  * (Note the "doesn't")
398  * @param $string code with strings
399  * @return code with strings removed
400  */
401 function remove_strings($string) {
402   $result= "";
403   
404   $inside_string=false;
405   $inside_string_uni=false;
406   $inside_string_double=false;
407   
408   // Walk through $string
409   for($i=0;$i<strlen($string);$i++) {
410     if($string[$i]=="'" && ($i>0 && ($string[$i-1] != "\\"))) {
411       if(!$inside_string_uni) {
412         // We're now inside
413         if(!$inside_string_double) {
414           $inside_string_uni= true;
415           $inside_string= true;
416         }
417       } else {
418         // We're now outside
419         $inside_string_uni= false;
420         if(!$inside_string_double) {
421           $inside_string= false;
422         }
423       }
424     } else if($string[$i]=='"' && ($i>0 && ($string[$i-1] != "\\"))) {
425       if(!$inside_string_double) {
426         // We're now inside
427         if(!$inside_string_uni) {
428           $inside_string_double= true;
429           $inside_string= true;
430         }
431       } else {
432         // We're now outside
433         $inside_string_double= false;
434         if(!$inside_string_uni) {
435           $inside_string= false;
436         }
437       }
438     } else {
439       // Push char to $result if not inside string
440       $result.= (!$inside_string)?$string[$i]:"";
441     }
442   }
444   // Return string
445   return $result;
448 /** 
449  * Scans directory $dir for files (filenames) matching regular expression $pattern
450  * @param $dir Initial Directory to start scan
451  * @param $pattern Regex-Pattern to match on files to scan
452  * @param $skip_dirs Regex-Patten to match on directories to skip
453  * @return file list
454  */
455 function list_directory($dir, $pattern, $skip_dirs) {
456   $file_list= '';
457   $stack[]= $dir;
458   while ($stack) {
459     $current_dir= array_pop($stack);
460     if ($dh= opendir($current_dir)) {
461       while (($file= readdir($dh)) !== false) {
462          if ($file !== '.' && $file !== '..' && preg_match("/$skip_dirs/",$file)==0) {
463           $current_file= "{$current_dir}/{$file}";
464           if (is_file($current_file) && preg_match($pattern, $current_file)) {
465             $file_list[]= "{$current_dir}/{$file}";
466           } elseif (is_dir($current_file)) {
467             $stack[]= $current_file;
468           }
469         }
470       }
471     }
472   }
473   return $file_list;
476 /**
477  * 'Flats' a multi-dimensional array. Keys are newly (incrementally) created by PHP.
478  */
479 function array_values_recursive($array) {
480   $arrayValues = array();
481   
482   foreach ($array as $value) {
483     if (is_scalar($value) OR is_resource($value)) {
484       $arrayValues[] = $value;
485     } elseif (is_array($value)) {
486       $arrayValues = array_merge($arrayValues, array_values_recursive($value));
487     }
488   }
490   return $arrayValues;
493 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
494 ?>