├── LICENSE ├── README.markdown └── wp-phptidy.php /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 675 Mass Ave, Cambridge, MA 02139, USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | The story: 4 | 5 | 1. Magnus Rosenbaum wrote the original [phptidy](http://phptidy.berlios.de/) script. 6 | 2. Eoin Gallagher modified it so that it conforms to the [WordPress Coding Standards](http://codex.wordpress.org/WordPress_Coding_Standards). 7 | 3. I put Eoin's version on github, for convenience. 8 | 9 | ## Basic usage 10 | 11 | ``` 12 | $ wp-phptidy.php replace some-file.php 13 | ``` 14 | 15 | Complete documentation can be found on the phptidy home page: http://phptidy.berlios.de/ 16 | -------------------------------------------------------------------------------- /wp-phptidy.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | = 5.0 12 | * 13 | * @copyright 2003-2008 Magnus Rosenbaum 14 | * @license GPL v2 15 | * 16 | * This program is free software; you can redistribute it and/or 17 | * modify it under the terms of the GNU General Public License 18 | * as published by the Free Software Foundation; either version 2 19 | * of the License, or (at your option) any later version. 20 | * 21 | * This program is distributed in the hope that it will be useful, 22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | * GNU General Public License for more details. 25 | * 26 | * You should have received a copy of the GNU General Public License 27 | * along with this program; if not, write to the Free Software 28 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 29 | * 30 | * @version 2.9 (2009-01-07) 31 | * @author Magnus Rosenbaum 32 | * @package phptidy 33 | */ 34 | 35 | 36 | //////////////// DEFAULT CONFIGURATION /////////////////// 37 | 38 | // You can overwrite all these settings in your configuration files. 39 | 40 | // List of files in your project 41 | // Wildcards for glob() may be used. 42 | // Example: array("*.php", "inc/*.php"); 43 | $project_files = array(); 44 | 45 | // List of files you want to exclude from the project files 46 | // Wildcards are not allowed here. 47 | // Example: array("inc/external_lib.php"); 48 | $project_files_excludes = array(); 49 | 50 | // The automatically added author in the phpdoc file docblocks 51 | // If left empty no new @author doctags will be added. 52 | // Example: "Your Name " 53 | $default_author = ""; 54 | 55 | // Name of the automatically added @package doctag in the phpdoc file docblocks 56 | // Example: "myproject" 57 | $default_package = ""; 58 | 59 | // String used for indenting 60 | // If you indent with spaces you can use as much spaces as you like. 61 | // Useful values: "\t" for indenting with tabs, 62 | // " " for indenting with two spaces 63 | $indent_char = "\t"; 64 | 65 | // Control structures with the opening curly brace on a new line 66 | // Examples: false always on the same line 67 | // true always on a new line 68 | // array(T_CLASS, T_FUNCTION) for PEAR Coding Standards 69 | $curly_brace_newline = false; 70 | 71 | // PHP open tag 72 | // All php open tags will be replaced by the here defined kind of open tag. 73 | // Useful values: "=" ) ) { 118 | echo "Error: phptidy requires PHP 5 or newer.\n"; 119 | exit( 1 ); 120 | } 121 | 122 | if ( php_sapi_name() != "cli" ) { 123 | echo "Error: phptidy has to be run on command line with CLI SAPI\n"; 124 | exit( 1 ); 125 | } 126 | 127 | 128 | // Read command line 129 | $command = ""; 130 | $files = array(); 131 | $options = array(); 132 | foreach ( $_SERVER['argv'] as $key => $value ) { 133 | if ( $key==0 ) continue; 134 | if ( $key==1 ) { 135 | $command = $value; 136 | continue; 137 | } 138 | if ( substr( $value, 0, 1 )=="-" ) { 139 | $options[] = $value; 140 | } else { 141 | $files[] = $value; 142 | } 143 | } 144 | 145 | // Get command 146 | switch ( $command ) { 147 | case "help": 148 | case "--help": 149 | case "-h": 150 | usage(); 151 | exit; 152 | case "suffix": 153 | case "replace": 154 | case "diff": 155 | case "source": 156 | case "files": 157 | case "tokens": 158 | break; 159 | default: 160 | echo "Unknown command: '".$command."'\n"; 161 | case "": 162 | usage(); 163 | exit( 1 ); 164 | } 165 | 166 | // Get options 167 | $verbose = false; 168 | foreach ( $options as $option ) { 169 | switch ( $option ) { 170 | case "-v": 171 | case "--verbose": 172 | $verbose = true; 173 | continue 2; 174 | } 175 | echo "Unknown option: '".$option."'\n"; 176 | usage(); 177 | exit( 1 ); 178 | } 179 | 180 | // Load config file 181 | if ( file_exists( CONFIGFILE ) ) { 182 | verbose( "Using configuration file ".CONFIGFILE."\n" ); 183 | require CONFIGFILE; 184 | } else { 185 | verbose( "Running without configuration file\n" ); 186 | } 187 | 188 | // Files from config file 189 | if ( !count( $files ) ) { 190 | if ( !count( $project_files ) ) { 191 | echo "Error: No files supplied on commandline and also no project files specified in config file\n"; 192 | exit( 1 ); 193 | } 194 | foreach ( $project_files as $pf ) { 195 | $files = array_unique( array_merge( $files, glob( $pf ) ) ); 196 | } 197 | } 198 | 199 | // File excludes from config file 200 | foreach ( $project_files_excludes as $file_exclude ) { 201 | if ( 202 | ( $key = array_search( $file_exclude, $files ) ) !== false 203 | ) unset( $files[$key] ); 204 | } 205 | 206 | // Check files 207 | foreach ( $files as $key => $file ) { 208 | // Ignore backups and results from phptidy 209 | if ( 210 | substr( $file, -12 )==".phptidybak~" or 211 | substr( $file, -12 )==".phptidy.php" 212 | ) { 213 | unset( $files[$key] ); 214 | continue; 215 | } 216 | if ( !is_readable( $file ) or !is_file( $file ) ) { 217 | echo "Error: File '".$file."' does not exist or is not readable\n"; 218 | exit( 1 ); 219 | } 220 | } 221 | 222 | // Show files 223 | if ( $command=="files" ) { 224 | print_r( $files ); 225 | exit; 226 | } 227 | 228 | // Read cache file 229 | if ( file_exists( CACHEFILE ) ) { 230 | verbose( "Using cache file ".CACHEFILE."\n" ); 231 | $cache = unserialize( file_get_contents( CACHEFILE ) ); 232 | $cache_orig = $cache; 233 | } else { 234 | $cache = array( 235 | 'md5sums' => array(), 236 | ); 237 | $cache_orig = false; 238 | } 239 | 240 | // Find functions and includes 241 | verbose( "Find functions and includes " ); 242 | $functions = array(); 243 | $seetags = array(); 244 | foreach ( $files as $file ) { 245 | verbose( "." ); 246 | $source = file_get_contents( $file ); 247 | $functions = array_unique( array_merge( $functions, get_functions( $source ) ) ); 248 | find_includes( $seetags, $source, $file ); 249 | } 250 | verbose( "\n" ); 251 | 252 | $md5sum = md5( serialize( $functions ).serialize( $seetags ) ); 253 | if ( isset( $cache['functions_seetags'] ) and $md5sum == $cache['functions_seetags'] ) { 254 | // Use cache only if functions and seetags haven't changed 255 | $use_cache = true; 256 | } else { 257 | $use_cache = false; 258 | $cache['functions_seetags'] = $md5sum; 259 | } 260 | 261 | if ( !extension_loaded( "tokenizer" ) ) { 262 | echo "Error: The 'Tokenizer' extension for PHP is missing. See http://php.net/manual/en/book.tokenizer.php for more information.\n"; 263 | exit( 1 ); 264 | } 265 | 266 | verbose( "Process files\n" ); 267 | $replaced = 0; 268 | foreach ( $files as $file ) { 269 | 270 | verbose( " ".$file."\n" ); 271 | $source_orig = file_get_contents( $file ); 272 | 273 | // Cache 274 | $md5sum = md5( $source_orig ); 275 | if ( $use_cache and isset( $cache['md5sums'][$file] ) and $md5sum == $cache['md5sums'][$file] ) { 276 | // Original file has not changed, so we don't process it 277 | verbose( " File unchanged since last processing.\n" ); 278 | continue; 279 | } 280 | 281 | // Check encoding 282 | if ( $encoding and !mb_check_encoding( $source_orig, $encoding ) ) { 283 | echo " File contains characters which are not valid in ".$encoding.":\n"; 284 | $source_converted = mb_convert_encoding( $source_orig, $encoding ); 285 | $tmpfile = "/tmp/tmp.phptidy.php"; 286 | if ( !file_put_contents( $tmpfile, $source_converted ) ) { 287 | echo "Error: The temporary file '".$tmpfile."' could not be saved.\n"; 288 | exit( 1 ); 289 | } 290 | system( "echo -ne '\\033[01;31m'; diff -u ".$file." ".$tmpfile." 2>&1; echo -ne '\\033[00m'" ); 291 | } 292 | 293 | // Process source code 294 | $source = $source_orig; 295 | $count = 0; 296 | do { 297 | $source_in = $source; 298 | $source = phptidy( $source_in ); 299 | ++$count; 300 | if ( $count > 3 ) { 301 | echo " Code processed 3 times and still not consistent!\n"; 302 | break; 303 | } 304 | } while ( $source != $source_in ); 305 | 306 | // Processing has not changed content of file 307 | if ( $count == 1 ) { 308 | verbose( " Processed without changes.\n" ); 309 | // Write md5sum of the unchanged file into cache 310 | $cache['md5sums'][$file] = $md5sum; 311 | continue; 312 | } 313 | 314 | // Output 315 | switch ( $command ) { 316 | case "suffix": 317 | 318 | $newfile = $file.".phptidy.php"; 319 | if ( !file_put_contents( $newfile, $source ) ) { 320 | echo "Error: The file '".$newfile."' could not be saved.\n"; 321 | exit( 1 ); 322 | } 323 | verbose( " ".$newfile." saved.\n" ); 324 | 325 | break; 326 | case "replace": 327 | 328 | $backupfile = dirname( $file ).( dirname( $file )?"/":"" ).".".basename( $file ).".phptidybak~"; 329 | if ( !copy( $file, $backupfile ) ) { 330 | echo "Error: The file '".$backupfile."' could not be saved.\n"; 331 | exit( 1 ); 332 | } 333 | if ( !file_put_contents( $file, $source ) ) { 334 | echo "Error: The file '".$file."' could not be overwritten.\n"; 335 | exit( 1 ); 336 | } 337 | verbose( " replaced.\n" ); 338 | ++$replaced; 339 | 340 | // Write new md5sum into cache 341 | $cache['md5sums'][$file] = md5( $source ); 342 | 343 | break; 344 | case "diff": 345 | 346 | $tmpfile = "/tmp/tmp.phptidy.php"; 347 | if ( !file_put_contents( $tmpfile, $source ) ) { 348 | echo "Error: The temporary file '".$tmpfile."' could not be saved.\n"; 349 | exit( 1 ); 350 | } 351 | system( "echo -ne '\\033[01;34m'; diff -u ".$file." ".$tmpfile." 2>&1; echo -ne '\\033[00m'" ); 352 | 353 | break; 354 | case "source": 355 | 356 | echo $source; 357 | 358 | break; 359 | } 360 | 361 | } 362 | 363 | if ( $command=="replace" ) { 364 | if ( $replaced ) { 365 | verbose( "Replaced ".$replaced." files.\n" ); 366 | } 367 | if ( $cache != $cache_orig ) { 368 | verbose( "Write cache file ".CACHEFILE."\n" ); 369 | if ( !file_put_contents( CACHEFILE, serialize( $cache ) ) ) { 370 | echo "Warning: The cache file '".CACHEFILE."' could not be saved.\n"; 371 | } 372 | } 373 | } 374 | 375 | 376 | /////////////////// FUNCTIONS ////////////////////// 377 | 378 | /** 379 | * Output script status messages 380 | */ 381 | function verbose( $msg ) { 382 | if ( $GLOBALS['verbose'] ) echo $msg; 383 | } 384 | 385 | /** 386 | * Display usage information 387 | */ 388 | function usage() { 389 | echo " 390 | Usage: phptidy.php command [files|options] 391 | 392 | Commands: 393 | suffix Write output into files with suffix .phptidy.php 394 | replace Replace files and backup original as .phptidybak 395 | diff Show diff between old and new source 396 | source Show processed source code of affected files 397 | files Show files that would be processed 398 | tokens Show source file tokens 399 | help Display this message 400 | 401 | Options: 402 | -v Verbose messages 403 | 404 | If no files are supplied on command line, they will be read from the config 405 | file. 406 | 407 | See README and source comments for more information. 408 | 409 | "; 410 | } 411 | 412 | 413 | /** 414 | * Clean up source code 415 | * 416 | * @param string $source 417 | * @return string 418 | */ 419 | function phptidy( $source ) { 420 | 421 | // Replace non-Unix line breaks 422 | // http://pear.php.net/manual/en/standards.file.php 423 | // Windows line breaks -> Unix line breaks 424 | $source = str_replace( "\r\n", "\n", $source ); 425 | // Mac line breaks -> Unix line breaks 426 | $source = str_replace( "\r", "\n", $source ); 427 | 428 | $tokens = get_tokens( $source ); 429 | 430 | if ( $GLOBALS['command']=="tokens" ) { 431 | print_tokens( $tokens ); 432 | exit; 433 | } 434 | 435 | // Simple formatting 436 | if ( $GLOBALS['fix_token_case'] ) fix_token_case( $tokens ); 437 | if ( $GLOBALS['fix_builtin_functions_case'] ) fix_builtin_functions_case( $tokens ); 438 | if ( $GLOBALS['replace_inline_tabs'] ) replace_inline_tabs( $tokens ); 439 | if ( $GLOBALS['replace_phptags'] ) replace_phptags( $tokens ); 440 | if ( $GLOBALS['replace_shell_comments'] ) replace_shell_comments( $tokens ); 441 | if ( $GLOBALS['fix_statement_brackets'] ) fix_statement_brackets( $tokens ); 442 | if ( $GLOBALS['fix_separation_whitespace'] ) fix_separation_whitespace( $tokens ); 443 | if ( $GLOBALS['fix_comma_space'] ) fix_comma_space( $tokens ); 444 | if ( $GLOBALS['fix_round_bracket_space'] ) fix_round_bracket_space( $tokens ); 445 | 446 | // PhpDocumentor 447 | if ( $GLOBALS['add_doctags'] ) { 448 | list( $usestags, $paramtags, $returntags ) = collect_doctags( $tokens ); 449 | //print_r($usestags); 450 | //print_r($paramtags); 451 | //print_r($returntags); 452 | } 453 | if ( $GLOBALS['add_file_docblock'] ) add_file_docblock( $tokens ); 454 | if ( $GLOBALS['add_function_docblocks'] ) add_function_docblocks( $tokens ); 455 | if ( $GLOBALS['add_doctags'] ) { 456 | add_doctags( $tokens, $usestags, $paramtags, $returntags, $GLOBALS['seetags'] ); 457 | } 458 | if ( $GLOBALS['fix_docblock_format'] ) fix_docblock_format( $tokens ); 459 | if ( $GLOBALS['fix_docblock_space'] ) fix_docblock_space( $tokens ); 460 | 461 | if ( $GLOBALS['add_blank_lines'] ) add_blank_lines( $tokens ); 462 | 463 | // Indenting 464 | if ( $GLOBALS['indent'] ) { 465 | indent( $tokens ); 466 | strip_closetag_indenting( $tokens ); 467 | } 468 | 469 | $source = combine_tokens( $tokens ); 470 | 471 | // Strip trailing whitespace 472 | $source = preg_replace( "/[ \t]+\n/", "\n", $source ); 473 | 474 | if ( substr( $source, -1 )!="\n" ) { 475 | // Add one line break at the end of the file 476 | // http://pear.php.net/manual/en/standards.file.php 477 | $source .= "\n"; 478 | } else { 479 | // Strip empty lines at the end of the file 480 | while ( substr( $source, -2 )=="\n\n" ) $source = substr( $source, 0, -1 ); 481 | } 482 | 483 | return $source; 484 | } 485 | 486 | 487 | //////////////// TOKEN FUNCTIONS /////////////////// 488 | 489 | 490 | /** 491 | * Returns the text part of a token 492 | * 493 | * @param mixed $token 494 | * @return string 495 | */ 496 | function token_text( $token ) { 497 | if ( is_string( $token ) ) return $token; 498 | return $token[1]; 499 | } 500 | 501 | 502 | /** 503 | * Prints all tokens 504 | * 505 | * @param array $tokens 506 | */ 507 | function print_tokens( $tokens ) { 508 | foreach ( $tokens as $token ) { 509 | if ( is_string( $token ) ) { 510 | echo $token."\n"; 511 | } else { 512 | list( $id, $text ) = $token; 513 | echo token_name( $id )." ".addcslashes( $text, "\0..\40!@\@\177..\377" )."\n"; 514 | } 515 | } 516 | } 517 | 518 | 519 | /** 520 | * Wrapper for token_get_all(), because there is new mysterious index 2 ... 521 | * 522 | * @param string $source 523 | * @return array 524 | */ 525 | function get_tokens( &$source ) { 526 | $tokens = token_get_all( $source ); 527 | foreach ( $tokens as &$token ) { 528 | if ( isset( $token[2] ) ) unset( $token[2] ); 529 | } 530 | return $tokens; 531 | } 532 | 533 | 534 | /** 535 | * Combines the tokens to the source code 536 | * 537 | * @param array $tokens 538 | * @return string 539 | */ 540 | function combine_tokens( $tokens ) { 541 | $out = ""; 542 | foreach ( $tokens as $key => $token ) { 543 | if ( is_string( $token ) ) { 544 | $out .= $token; 545 | } else { 546 | $out .= $token[1]; 547 | } 548 | } 549 | return $out; 550 | } 551 | 552 | 553 | /** 554 | * Displays a possible syntax error 555 | * 556 | * @param array $tokens 557 | * @param integer $key 558 | * @param string $message (optional) 559 | */ 560 | function possible_syntax_error( $tokens, $key, $message="" ) { 561 | echo "Possible syntax error detected"; 562 | if ( $message ) echo " (".$message.")"; 563 | echo ":\n"; 564 | echo combine_tokens( array_slice( $tokens, max( 0, $key-5 ), 10 ) )."\n"; 565 | } 566 | 567 | 568 | /** 569 | * Removes whitespace from the beginning of a token array 570 | * 571 | * @param array $tokens 572 | */ 573 | function tokens_ltrim( &$tokens ) { 574 | while ( 575 | isset( $tokens[0][0] ) and 576 | $tokens[0][0] === T_WHITESPACE 577 | ) { 578 | array_splice( $tokens, 0, 1 ); 579 | } 580 | } 581 | 582 | 583 | /** 584 | * Removes whitespace from the end of a token array 585 | * 586 | * @param array $tokens (reference) 587 | */ 588 | function tokens_rtrim( &$tokens ) { 589 | while ( 590 | isset( $tokens[$k=count( $tokens )-1][0] ) and 591 | $tokens[$k][0] === T_WHITESPACE 592 | ) { 593 | array_splice( $tokens, -1 ); 594 | } 595 | } 596 | 597 | 598 | /** 599 | * Removes all whitespace 600 | * 601 | * @param array $tokens (reference) 602 | */ 603 | function strip_whitespace( &$tokens ) { 604 | foreach ( $tokens as $key => $token ) { 605 | if ( 606 | isset( $token[0] ) and 607 | $token[0] === T_WHITESPACE 608 | ) { 609 | unset( $tokens[$key] ); 610 | } 611 | } 612 | $tokens = array_values( $tokens ); 613 | } 614 | 615 | 616 | /** 617 | * Gets the argument of a statement 618 | * 619 | * @param array $tokens 620 | * @param integer $key Key of the token of the command for which we want the argument 621 | * @return array 622 | */ 623 | function get_argument_tokens( &$tokens, $key ) { 624 | 625 | $tokens_arg = array(); 626 | 627 | $round_braces_count = 0; 628 | $curly_braces_count = 0; 629 | 630 | ++$key; 631 | while ( isset( $tokens[$key] ) ) { 632 | $token = &$tokens[$key]; 633 | 634 | if ( is_string( $token ) ) { 635 | if ( $token === ";" ) break; 636 | } else { 637 | if ( $token[0] === T_CLOSE_TAG ) break; 638 | } 639 | 640 | if ( $token === "(" ) { 641 | ++$round_braces_count; 642 | } elseif ( $token === ")" ) { 643 | --$round_braces_count; 644 | } elseif ( 645 | $token === "{" or ( 646 | is_array( $token ) and ( 647 | $token[0] === T_CURLY_OPEN or 648 | $token[0] === T_DOLLAR_OPEN_CURLY_BRACES 649 | ) 650 | ) 651 | ) { 652 | ++$curly_braces_count; 653 | } elseif ( $token === "}" ) { 654 | --$curly_braces_count; 655 | } 656 | 657 | if ( $round_braces_count < 0 or $round_braces_count < 0 ) break; 658 | 659 | $tokens_arg[] = $token; 660 | 661 | ++$key; 662 | } 663 | 664 | return $tokens_arg; 665 | } 666 | 667 | 668 | //////////////// FORMATTING FUNCTIONS /////////////////// 669 | 670 | 671 | /** 672 | * Checks for some tokens which must not be touched 673 | * 674 | * @param array $token 675 | * @return boolean 676 | */ 677 | function token_is_taboo( &$token ) { 678 | return ( 679 | // Do not touch HTML content 680 | $token[0] === T_INLINE_HTML or 681 | $token[0] === T_CLOSE_TAG or 682 | // Do not touch the content of strings 683 | $token[0] === T_CONSTANT_ENCAPSED_STRING or 684 | $token[0] === T_ENCAPSED_AND_WHITESPACE or 685 | // Do not touch the content of multiline comments 686 | ( $token[0] === T_COMMENT and substr( $token[1], 0, 2 ) === "/*" ) 687 | ); 688 | } 689 | 690 | 691 | /** 692 | * Converts commands to lower case 693 | * 694 | * @param array $tokens (reference) 695 | */ 696 | function fix_token_case( &$tokens ) { 697 | 698 | static $lower_case_tokens = array( 699 | T_ABSTRACT, 700 | T_ARRAY, 701 | T_ARRAY_CAST, 702 | T_AS, 703 | T_BOOL_CAST, 704 | T_BREAK, 705 | T_CASE, 706 | T_CATCH, 707 | T_CLASS, 708 | T_CLONE, 709 | T_CONST, 710 | T_CONTINUE, 711 | T_DECLARE, 712 | T_DEFAULT, 713 | T_DO, 714 | T_DOUBLE_CAST, 715 | T_ECHO, 716 | T_ELSE, 717 | T_ELSEIF, 718 | T_EMPTY, 719 | T_ENDDECLARE, 720 | T_ENDFOR, 721 | T_ENDFOREACH, 722 | T_ENDIF, 723 | T_ENDSWITCH, 724 | T_ENDWHILE, 725 | T_EVAL, 726 | T_EXIT, 727 | T_EXTENDS, 728 | T_FINAL, 729 | T_FOR, 730 | T_FOREACH, 731 | T_FUNCTION, 732 | T_GLOBAL, 733 | T_IF, 734 | T_IMPLEMENTS, 735 | T_INCLUDE, 736 | T_INCLUDE_ONCE, 737 | T_INSTANCEOF, 738 | T_INT_CAST, 739 | T_INTERFACE, 740 | T_ISSET, 741 | T_LIST, 742 | T_LOGICAL_AND, 743 | T_LOGICAL_OR, 744 | T_LOGICAL_XOR, 745 | T_NEW, 746 | T_OBJECT_CAST, 747 | T_PRINT, 748 | T_PRIVATE, 749 | T_PUBLIC, 750 | T_PROTECTED, 751 | T_REQUIRE, 752 | T_REQUIRE_ONCE, 753 | T_RETURN, 754 | T_STATIC, 755 | T_STRING_CAST, 756 | T_SWITCH, 757 | T_THROW, 758 | T_TRY, 759 | T_UNSET, 760 | T_UNSET_CAST, 761 | T_VAR, 762 | T_WHILE 763 | ); 764 | 765 | foreach ( $tokens as &$token ) { 766 | if ( is_string( $token ) ) continue; 767 | if ( $token[1] === strtolower( $token[1] ) ) continue; 768 | if ( in_array( $token[0], $lower_case_tokens ) ) { 769 | $token[1] = strtolower( $token[1] ); 770 | } 771 | } 772 | 773 | } 774 | 775 | 776 | /** 777 | * Converts builtin functions to lower case 778 | * 779 | * @param array $tokens (reference) 780 | */ 781 | function fix_builtin_functions_case( &$tokens ) { 782 | 783 | static $defined_internal_functions = false; 784 | if ( $defined_internal_functions === false ) { 785 | $defined_functions = get_defined_functions(); 786 | $defined_internal_functions = $defined_functions['internal']; 787 | } 788 | 789 | foreach ( $tokens as $key => &$token ) { 790 | 791 | if ( 792 | is_string( $token ) or 793 | $token[0] !== T_STRING or 794 | !isset( $tokens[$key+2] ) or 795 | // Ignore object methods 796 | ( is_array( $tokens[$key-1] ) and $tokens[$key-1][0] === T_OBJECT_OPERATOR ) 797 | ) continue; 798 | 799 | if ( 800 | $tokens[$key+1] === "(" 801 | ) { 802 | $lowercase = strtolower( $token[1] ); 803 | if ( 804 | $token[1] !== $lowercase and 805 | in_array( $lowercase, $defined_internal_functions ) 806 | ) { 807 | $token[1] = $lowercase; 808 | } 809 | } elseif ( 810 | $tokens[$key+2] === "(" and 811 | is_array( $tokens[$key+1] ) and $tokens[$key+1][0] === T_WHITESPACE 812 | ) { 813 | if ( 814 | in_array( strtolower( $token[1] ), $defined_internal_functions ) 815 | ) { 816 | $token[1] = strtolower( $token[1] ); 817 | // Remove whitespace between function name and opening round bracket 818 | unset( $tokens[$key+1] ); 819 | } 820 | } 821 | 822 | } 823 | 824 | $tokens = array_values( $tokens ); 825 | } 826 | 827 | 828 | /** 829 | * Replaces inline tabs with spaces 830 | * 831 | * @param array $tokens (reference) 832 | */ 833 | function replace_inline_tabs( &$tokens ) { 834 | 835 | foreach ( $tokens as &$token ) { 836 | 837 | if ( is_string( $token ) ) { 838 | $text =& $token; 839 | } else { 840 | if ( token_is_taboo( $token ) ) continue; 841 | $text =& $token[1]; 842 | } 843 | 844 | // Replace one tab with one space 845 | $text = str_replace( "\t", " ", $text ); 846 | 847 | } 848 | 849 | } 850 | 851 | 852 | /** 853 | * Replaces PHP-Open-Tags with consistent tags 854 | * 855 | * @param array $tokens (reference) 856 | */ 857 | function replace_phptags( &$tokens ) { 858 | 859 | foreach ( $tokens as $key => &$token ) { 860 | if ( is_string( $token ) ) continue; 861 | 862 | switch ( $token[0] ) { 863 | case T_OPEN_TAG: 864 | 865 | // The open tag is already the right one 866 | if ( rtrim( $token[1] ) == $GLOBALS['open_tag'] ) continue; 867 | 868 | // Collect following whitespace 869 | preg_match( "/\s*$/", $token[1], $matches ); 870 | $whitespace = $matches[0]; 871 | if ( $tokens[$key+1][0] === T_WHITESPACE ) { 872 | $whitespace .= $tokens[$key+1][1]; 873 | array_splice( $tokens, $key+1, 1 ); 874 | } 875 | 876 | if ( $GLOBALS['open_tag']==" &$token ) { 976 | 977 | if ( is_string( $token ) or !in_array( $token[0], $statement_tokens ) ) continue; 978 | 979 | $tokens_arg = get_argument_tokens( $tokens, $key ); 980 | $tokens_arg_orig = $tokens_arg; 981 | 982 | tokens_ltrim( $tokens_arg ); 983 | 984 | if ( !count( $tokens_arg ) or $tokens_arg[0] !== "(" ) continue; 985 | 986 | tokens_rtrim( $tokens_arg ); 987 | 988 | // Check if the opening bracket has a matching one at the end of the expression 989 | $round_braces_count = 0; 990 | foreach ( $tokens_arg as $k => $t ) { 991 | if ( is_string( $t ) ) { 992 | if ( $t === "(" ) ++$round_braces_count; 993 | elseif ( $t === ")" ) --$round_braces_count; 994 | else continue; 995 | // Check if the expression begins without a bracket or if the bracket was closed before the end of the expression was reached 996 | if ( $round_braces_count == 0 and $k != count( $tokens_arg )-1 ) { 997 | continue 2; 998 | } 999 | if ( $round_braces_count < 0 ) { 1000 | possible_syntax_error( $tokens, $key, "Closing round bracket found which has not been opened" ); 1001 | continue 2; 1002 | } 1003 | } else { 1004 | // Do not touch multiline expressions 1005 | if ( $t[0] === T_WHITESPACE and strpos( $t[1], "\n" )!==false ) { 1006 | continue 2; 1007 | } 1008 | } 1009 | } 1010 | // Detect missing brackets 1011 | if ( $round_braces_count != 0 ) { 1012 | possible_syntax_error( $tokens, $key, "Round bracket opened but no matching closing bracket found" ); 1013 | continue; 1014 | } 1015 | 1016 | // Remove the outermost brackets 1017 | $tokens_arg = array_slice( $tokens_arg, 1, -1 ); 1018 | 1019 | tokens_ltrim( $tokens_arg ); 1020 | tokens_rtrim( $tokens_arg ); 1021 | 1022 | // Add one space between the command and the argument if the argument is not empty 1023 | if ( $tokens_arg ) { 1024 | array_unshift( $tokens_arg, array( T_WHITESPACE, " " ) ); 1025 | } 1026 | 1027 | array_splice( $tokens, $key+1, count( $tokens_arg_orig ), $tokens_arg ); 1028 | 1029 | } 1030 | 1031 | } 1032 | 1033 | 1034 | /** 1035 | * Fixes whitespace between commands and braces 1036 | * 1037 | * @param array $tokens (reference) 1038 | */ 1039 | function fix_separation_whitespace( &$tokens ) { 1040 | 1041 | $control_structure = false; 1042 | 1043 | foreach ( $tokens as $key => &$token ) { 1044 | if ( is_string( $token ) ) { 1045 | 1046 | // Exactly 1 space or a newline between closing round bracket and opening curly bracket 1047 | if ( $tokens[$key] === ")" ) { 1048 | if ( 1049 | isset( $tokens[$key+1] ) and $tokens[$key+1] === "{" 1050 | ) { 1051 | // Insert an additional space or newline before the bracket 1052 | array_splice( $tokens, $key+1, 0, array( 1053 | array( T_WHITESPACE, separation_whitespace( $control_structure ) ) 1054 | ) ); 1055 | } elseif ( 1056 | isset( $tokens[$key+1][0] ) and $tokens[$key+1][0] === T_WHITESPACE and 1057 | isset( $tokens[$key+2] ) and $tokens[$key+2] === "{" 1058 | ) { 1059 | // Set the existing whitespace before the bracket to exactly one space or newline 1060 | $tokens[$key+1][1] = separation_whitespace( $control_structure ); 1061 | } 1062 | } 1063 | 1064 | } else { 1065 | 1066 | switch ( $token[0] ) { 1067 | case T_CLASS: 1068 | // Class definition 1069 | if ( 1070 | isset( $tokens[$key+1][0] ) and $tokens[$key+1][0] === T_WHITESPACE and 1071 | isset( $tokens[$key+2][0] ) and $tokens[$key+2][0] === T_STRING 1072 | ) { 1073 | // Exactly 1 space between 'class' and the class name 1074 | $tokens[$key+1][1] = " "; 1075 | // Exactly 1 space between the class name and the opening curly bracket 1076 | if ( $tokens[$key+3] === "{" ) { 1077 | // Insert an additional space or newline before the bracket 1078 | array_splice( $tokens, $key+3, 0, array( 1079 | array( T_WHITESPACE, separation_whitespace( T_CLASS ) ) 1080 | ) ); 1081 | } elseif ( 1082 | isset( $tokens[$key+3][0] ) and $tokens[$key+3][0] === T_WHITESPACE and 1083 | isset( $tokens[$key+4] ) and $tokens[$key+4] === "{" 1084 | ) { 1085 | // Set the existing whitespace before the bracket to exactly one space or a newline 1086 | $tokens[$key+3][1] = separation_whitespace( T_CLASS ); 1087 | } 1088 | } 1089 | break; 1090 | case T_FUNCTION: 1091 | // Function definition 1092 | if ( 1093 | isset( $tokens[$key+1][0] ) and $tokens[$key+1][0] === T_WHITESPACE and 1094 | isset( $tokens[$key+2][0] ) and $tokens[$key+2][0] === T_STRING 1095 | ) { 1096 | // Exactly 1 Space between 'function' and the function name 1097 | $tokens[$key+1][1] = " "; 1098 | // No whitespace between function name and opening round bracket 1099 | if ( isset( $tokens[$key+3][0] ) and $tokens[$key+3][0] === T_WHITESPACE ) { 1100 | // Remove the whitespace 1101 | array_splice( $tokens, $key+3, 1 ); 1102 | } 1103 | } 1104 | break; 1105 | case T_IF: 1106 | case T_ELSEIF: 1107 | case T_FOR: 1108 | case T_FOREACH: 1109 | case T_WHILE: 1110 | case T_SWITCH: 1111 | // At least 1 space between a statement and a opening round bracket 1112 | if ( $tokens[$key+1] === "(" ) { 1113 | // Insert an additional space or newline before the bracket 1114 | array_splice( $tokens, $key+1, 0, array( 1115 | array( T_WHITESPACE, separation_whitespace( T_SWITCH ) ), 1116 | ) ); 1117 | } 1118 | break; 1119 | case T_ELSE: 1120 | case T_DO: 1121 | // Exactly 1 space between a command and a opening curly bracket 1122 | if ( $tokens[$key+1] === "{" ) { 1123 | // Insert an additional space or newline before the bracket 1124 | array_splice( $tokens, $key+1, 0, array( 1125 | array( T_WHITESPACE, separation_whitespace( T_DO ) ), 1126 | ) ); 1127 | } elseif ( 1128 | isset( $tokens[$key+1][0] ) and $tokens[$key+1][0] === T_WHITESPACE and 1129 | isset( $tokens[$key+2] ) and $tokens[$key+2] === "{" 1130 | ) { 1131 | // Set the existing whitespace before the bracket to exactly one space or a newline 1132 | $tokens[$key+1][1] = separation_whitespace( T_DO ); 1133 | } 1134 | break; 1135 | default: 1136 | // Do not set $control_structure if the token is no control structure 1137 | continue 2; 1138 | } 1139 | 1140 | $control_structure = $token[0]; 1141 | 1142 | } 1143 | 1144 | } 1145 | 1146 | } 1147 | 1148 | 1149 | /** 1150 | * Whitespace before an opening curly bracket depending on the control structure 1151 | * 1152 | * @param integer $control_structure token of the control structure 1153 | * @return string 1154 | */ 1155 | function separation_whitespace( $control_structure ) { 1156 | if ( 1157 | $GLOBALS['curly_brace_newline']===true or ( 1158 | is_array( $GLOBALS['curly_brace_newline'] ) and 1159 | in_array( $control_structure, $GLOBALS['curly_brace_newline'] ) 1160 | ) 1161 | ) return "\n"; 1162 | return " "; 1163 | } 1164 | 1165 | 1166 | /** 1167 | * Adds one space after a comma 1168 | * 1169 | * @param array $tokens (reference) 1170 | */ 1171 | function fix_comma_space( &$tokens ) { 1172 | 1173 | foreach ( $tokens as $key => &$token ) { 1174 | if ( !is_string( $token ) ) continue; 1175 | if ( 1176 | // If the current token ends with a comma... 1177 | substr( $token, -1 ) === "," and 1178 | // ...and the next token is no whitespace 1179 | !( isset( $tokens[$key+1][0] ) and $tokens[$key+1][0] === T_WHITESPACE ) 1180 | ) { 1181 | // Insert one space 1182 | array_splice( $tokens, $key+1, 0, array( 1183 | array( T_WHITESPACE, " " ) 1184 | ) ); 1185 | } 1186 | } 1187 | 1188 | } 1189 | 1190 | /** 1191 | * Adds one space after a round bracket 1192 | * 1193 | * @param array $tokens (reference) 1194 | */ 1195 | function fix_round_bracket_space( &$tokens ) { 1196 | 1197 | foreach ( $tokens as $key => &$token ) { 1198 | if ( !is_string( $token ) ) continue; 1199 | if ( 1200 | // If the current token is a start round bracket... 1201 | $token === "(" and 1202 | // ...and the next token is no whitespace 1203 | !( isset( $tokens[$key+1][0] ) and $tokens[$key+1][0] === T_WHITESPACE ) and 1204 | // ...and the next token is not an end round bracket 1205 | !( isset( $tokens[$key+1][0] ) and $tokens[$key+1][0] === ')' ) 1206 | ) { 1207 | // Insert one space 1208 | array_splice( $tokens, $key+1, 0, array( 1209 | array( T_WHITESPACE, " " ) 1210 | ) ); 1211 | } 1212 | else if ( 1213 | // If the current token is an end round bracket... 1214 | $token === ")" and 1215 | // ...and the previous token is no whitespace 1216 | !( isset( $tokens[$key-1][0] ) and $tokens[$key-1][0] === T_WHITESPACE ) and 1217 | // ...and the previous token is a start round bracket 1218 | !( isset( $tokens[$key-1][0] ) and $tokens[$key-1][0] === '(' ) 1219 | ) { 1220 | // Insert one space 1221 | array_splice( $tokens, $key, 0, array( 1222 | array( T_WHITESPACE, " " ) 1223 | ) ); 1224 | } 1225 | } 1226 | } 1227 | 1228 | 1229 | /** 1230 | * Fixes the format of a DocBlock 1231 | * 1232 | * @param array $tokens (reference) 1233 | */ 1234 | function fix_docblock_format( &$tokens ) { 1235 | 1236 | foreach ( $tokens as $key => &$token ) { 1237 | 1238 | if ( is_string( $token ) or $token[0] !== T_DOC_COMMENT ) continue; 1239 | 1240 | $lines_orig = explode( "\n", $tokens[$key][1] ); 1241 | 1242 | $lines = array(); 1243 | $comments_started = false; 1244 | $doctags_started = false; 1245 | $last_line = false; 1246 | $param_max_variable_length = 0; 1247 | foreach ( $lines_orig as $line ) { 1248 | $line = trim( $line ); 1249 | // Strip empty lines 1250 | if ( $line=="" ) continue; 1251 | if ( $line!="/**" and $line!="*/" ) { 1252 | 1253 | // Add stars where missing 1254 | if ( substr( $line, 0, 1 )!="*" ) $line = "* ".$line; 1255 | elseif ( $line!="*" and substr( $line, 0, 2 )!="* " ) $line = "* ".substr( $line, 1 ); 1256 | 1257 | // Strip empty lines at the beginning 1258 | if ( !$comments_started ) { 1259 | if ( $line=="*" and count( $lines_orig )>3 ) continue; 1260 | $comments_started = true; 1261 | } 1262 | 1263 | if ( substr( $line, 0, 3 )=="* @" ) { 1264 | 1265 | // Add empty line before DocTags if missing 1266 | if ( !$doctags_started ) { 1267 | if ( $last_line!="*" ) $lines[] = "*"; 1268 | if ( $last_line=="/**" ) $lines[] = "*"; 1269 | $doctags_started = true; 1270 | } 1271 | 1272 | // DocTag format 1273 | if ( preg_match( '/^\* @param(\s+[^\s\$]*)?\s+(&?\$[^\s]+)/', $line, $matches ) ) { 1274 | $param_max_variable_length = max( $param_max_variable_length, strlen( $matches[2] ) ); 1275 | } 1276 | 1277 | } 1278 | 1279 | } 1280 | $lines[] = $line; 1281 | $last_line = $line; 1282 | } 1283 | 1284 | foreach ( $lines as $l => $line ) { 1285 | 1286 | // DocTag format 1287 | if ( preg_match( '/^\* @param(\s+([^\s\$]*))?(\s+(&?\$[^\s]+))?(.*)$/', $line, $matches ) ) { 1288 | $line = "* @param "; 1289 | if ( $matches[2] ) $line .= str_pad( $matches[2], 7 ); else $line .= "unknown"; 1290 | $line .= " "; 1291 | if ( $matches[4] ) $line .= str_pad( $matches[4], $param_max_variable_length )." "; 1292 | $line .= trim( $matches[5] ); 1293 | $lines[$l] = $line; 1294 | } 1295 | 1296 | } 1297 | 1298 | $token[1] = join( "\n", $lines ); 1299 | 1300 | } 1301 | 1302 | } 1303 | 1304 | 1305 | /** 1306 | * Adjusts empty lines after DocBlocks 1307 | * 1308 | * @param array $tokens (reference) 1309 | */ 1310 | function fix_docblock_space( &$tokens ) { 1311 | 1312 | $filedocblock = true; 1313 | 1314 | foreach ( $tokens as $key => &$token ) { 1315 | 1316 | if ( is_string( $token ) or $token[0] !== T_DOC_COMMENT ) continue; 1317 | 1318 | if ( $filedocblock ) { 1319 | 1320 | // Exactly 2 empty lines after the file DocBlock 1321 | if ( $tokens[$key+1][0] === T_WHITESPACE ) { 1322 | $tokens[$key+1][1] = preg_replace( "/\n([ \t]*\n)*/", "\n\n\n", $tokens[$key+1][1] ); 1323 | } 1324 | $filedocblock = false; 1325 | 1326 | } else { 1327 | 1328 | // Delete empty lines after the DocBlock 1329 | if ( $tokens[$key+1][0] === T_WHITESPACE ) { 1330 | $tokens[$key+1][1] = preg_replace( "/\n([ \t]*\n)+/", "\n", $tokens[$key+1][1] ); 1331 | } 1332 | 1333 | // Add empty lines before the DocBlock 1334 | if ( $tokens[$key-1][0] === T_WHITESPACE ) { 1335 | $n = 2; 1336 | if ( substr( token_text( $tokens[$key-2] ), -1 ) == "\n" ) --$n; 1337 | // At least 2 empty lines before the docblock of a function 1338 | if ( $tokens[$key+2][0] === T_FUNCTION ) ++$n; 1339 | if ( strpos( $tokens[$key-1][1], str_repeat( "\n", $n ) ) === false ) { 1340 | $tokens[$key-1][1] = preg_replace( "/(\n){1,".$n."}/", str_repeat( "\n", $n ), $tokens[$key-1][1] ); 1341 | } 1342 | } 1343 | 1344 | } 1345 | 1346 | } 1347 | 1348 | } 1349 | 1350 | 1351 | /** 1352 | * Adds 2 blank lines after functions and classes 1353 | * 1354 | * @param array $tokens (reference) 1355 | */ 1356 | function add_blank_lines( &$tokens ) { 1357 | 1358 | // Level of curly brackets 1359 | $curly_braces_count = 0; 1360 | 1361 | $curly_brace_opener = array(); 1362 | $control_structure = false; 1363 | 1364 | $heredoc_started = false; 1365 | 1366 | foreach ( $tokens as $key => &$token ) { 1367 | 1368 | // Skip HEREDOC 1369 | if ( $heredoc_started ) { 1370 | if ( isset( $token[0] ) and $token[0] === T_END_HEREDOC ) { 1371 | $heredoc_started = false; 1372 | } 1373 | continue; 1374 | } 1375 | 1376 | if ( is_array( $token ) ) { 1377 | 1378 | // Detect beginning of a HEREDOC block 1379 | if ( $token[0] === T_START_HEREDOC ) { 1380 | $heredoc_started = true; 1381 | continue; 1382 | } 1383 | 1384 | // Remember the type of control structure 1385 | if ( in_array( $token[0], array( T_IF, T_ELSEIF, T_WHILE, T_FOR, T_FOREACH, T_SWITCH, T_FUNCTION, T_CLASS ) ) ) { 1386 | $control_structure = $token[0]; 1387 | continue; 1388 | } 1389 | 1390 | } 1391 | 1392 | if ( $token === "}" ) { 1393 | 1394 | if ( 1395 | $curly_brace_opener[$curly_braces_count] === T_FUNCTION or 1396 | $curly_brace_opener[$curly_braces_count] === T_CLASS 1397 | ) { 1398 | 1399 | // At least 2 blank lines after a function or class 1400 | if ( 1401 | $tokens[$key+1][0] === T_WHITESPACE and 1402 | substr( $tokens[$key+1][1], 0, 2 ) != "\n\n\n" 1403 | ) { 1404 | $tokens[$key+1][1] = preg_replace( "/^([ \t]*\n){1,3}/", "\n\n\n", $tokens[$key+1][1] ); 1405 | } 1406 | 1407 | } 1408 | 1409 | --$curly_braces_count; 1410 | 1411 | } elseif ( 1412 | $token === "{" or ( 1413 | is_array( $token ) and ( 1414 | $token[0] === T_CURLY_OPEN or 1415 | $token[0] === T_DOLLAR_OPEN_CURLY_BRACES 1416 | ) 1417 | ) 1418 | ) { 1419 | 1420 | ++$curly_braces_count; 1421 | $curly_brace_opener[$curly_braces_count] = $control_structure; 1422 | 1423 | } 1424 | 1425 | } 1426 | 1427 | } 1428 | 1429 | 1430 | /** 1431 | * Indenting 1432 | * 1433 | * @param array $tokens (reference) 1434 | */ 1435 | function indent( &$tokens ) { 1436 | 1437 | // Level of curly brackets 1438 | $curly_braces_count = 0; 1439 | // Level of round brackets 1440 | $round_braces_count = 0; 1441 | 1442 | $round_brace_opener = false; 1443 | $round_braces_control = 0; 1444 | 1445 | // Number of opened control structures without curly brackets inside of a level of curly brackets 1446 | $control_structure = array( 0 ); 1447 | 1448 | $heredoc_started = false; 1449 | $trinity_started = false; 1450 | 1451 | foreach ( $tokens as $key => &$token ) { 1452 | 1453 | // Skip HEREDOC 1454 | if ( $heredoc_started ) { 1455 | if ( isset( $token[0] ) and $token[0] === T_END_HEREDOC ) { 1456 | $heredoc_started = false; 1457 | } 1458 | continue; 1459 | } 1460 | 1461 | // Detect beginning of a HEREDOC block 1462 | if ( isset( $token[0] ) and $token[0] === T_START_HEREDOC ) { 1463 | $heredoc_started = true; 1464 | continue; 1465 | } 1466 | 1467 | // The closing bracket itself has to be not indented again, so we decrease the brackets count before we reach the bracket. 1468 | if ( isset( $tokens[$key+1] ) ) { 1469 | if ( is_string( $tokens[$key+1] ) ) { 1470 | if ( 1471 | is_string( $token ) or 1472 | $token[0] !== T_WHITESPACE or 1473 | strpos( $token[1], "\n" )!==false 1474 | ) { 1475 | if ( $tokens[$key+1] === "}" ) --$curly_braces_count; 1476 | elseif ( $tokens[$key+1] === ")" ) --$round_braces_count; 1477 | } 1478 | } else { 1479 | if ( 1480 | // If the next token is a T_WHITESPACE without a \n, we have to look at the one after the next. 1481 | isset( $tokens[$key+2] ) and 1482 | $tokens[$key+1][0] === T_WHITESPACE and 1483 | strpos( $tokens[$key+1][1], "\n" )===false 1484 | ) { 1485 | if ( $tokens[$key+2] === "}" ) --$curly_braces_count; 1486 | elseif ( $tokens[$key+2] === ")" ) --$round_braces_count; 1487 | } 1488 | } 1489 | } 1490 | 1491 | if ( $token === "(" ) ++$round_braces_control; 1492 | elseif ( $token === ")" ) --$round_braces_control; 1493 | 1494 | if ( $token === "(" ) { 1495 | 1496 | if ( $round_braces_control==1 ) { 1497 | // Remember which command was before the bracket 1498 | $k = $key; 1499 | do { 1500 | --$k; 1501 | } while ( 1502 | isset( $tokens[$k] ) and ( 1503 | $tokens[$k][0] === T_WHITESPACE or 1504 | $tokens[$k][0] === T_STRING 1505 | ) 1506 | ); 1507 | if ( is_array( $tokens[$k] ) ) { 1508 | $round_brace_opener = $tokens[$k][0]; 1509 | } else { 1510 | $round_brace_opener = false; 1511 | } 1512 | } 1513 | 1514 | ++$round_braces_count; 1515 | 1516 | } elseif ( 1517 | ( 1518 | $token === ")" and 1519 | $round_braces_control == 0 and 1520 | in_array( 1521 | $round_brace_opener, 1522 | array( T_IF, T_ELSEIF, T_WHILE, T_FOR, T_FOREACH, T_SWITCH, T_FUNCTION ) 1523 | ) 1524 | ) or ( 1525 | is_array( $token ) and ( 1526 | $token[0] === T_ELSE or $token[0] === T_DO 1527 | ) 1528 | ) 1529 | ) { 1530 | // All control stuctures end with a curly bracket, except "else" and "do". 1531 | if ( isset( $control_structure[$curly_braces_count] ) ) { 1532 | ++$control_structure[$curly_braces_count]; 1533 | } else { 1534 | $control_structure[$curly_braces_count] = 1; 1535 | } 1536 | 1537 | } elseif ( $token === ";" or $token === "}" ) { 1538 | // After a command or a set of commands a control structure is closed. 1539 | if ( !empty( $control_structure[$curly_braces_count] ) ) --$control_structure[$curly_braces_count]; 1540 | 1541 | } else { 1542 | indent_text( 1543 | $tokens, 1544 | $key, 1545 | $curly_braces_count, 1546 | $round_braces_count, 1547 | $control_structure, 1548 | ( is_array( $token ) and $token[0] === T_DOC_COMMENT ), 1549 | $trinity_started 1550 | ); 1551 | 1552 | } 1553 | 1554 | if ( 1555 | $token === "{" or ( 1556 | is_array( $token ) and ( 1557 | $token[0] === T_CURLY_OPEN or 1558 | $token[0] === T_DOLLAR_OPEN_CURLY_BRACES 1559 | ) 1560 | ) 1561 | ) { 1562 | // If a curly bracket occurs, no command without brackets can follow. 1563 | if ( !empty( $control_structure[$curly_braces_count] ) ) --$control_structure[$curly_braces_count]; 1564 | ++$curly_braces_count; 1565 | // Inside of the new level of curly brackets it starts with no control structure. 1566 | $control_structure[$curly_braces_count] = 0; 1567 | } 1568 | 1569 | } 1570 | 1571 | } 1572 | 1573 | 1574 | /** 1575 | * Indents one token 1576 | * 1577 | * @param array $tokens (reference) 1578 | * @param integer $key 1579 | * @param integer $curly_braces_count 1580 | * @param integer $round_braces_count 1581 | * @param array $control_structure 1582 | * @param boolean $docblock 1583 | * @param boolean $trinity_started (reference) 1584 | */ 1585 | function indent_text( &$tokens, $key, $curly_braces_count, $round_braces_count, $control_structure, $docblock, &$trinity_started ) { 1586 | 1587 | if ( is_string( $tokens[$key] ) ) { 1588 | $text =& $tokens[$key]; 1589 | // If there is no line break it is only a inline string, not involved in indenting 1590 | if ( strpos( $text, "\n" )===false ) return; 1591 | } else { 1592 | $text =& $tokens[$key][1]; 1593 | // If there is no line break it is only a inline string, not involved in indenting 1594 | if ( strpos( $text, "\n" )===false ) return; 1595 | if ( token_is_taboo( $tokens[$key] ) ) return; 1596 | } 1597 | 1598 | $indent = $curly_braces_count + $round_braces_count; 1599 | for ( $i=0; $i<=$curly_braces_count; ++$i ) { 1600 | $indent += $control_structure[$i]; 1601 | } 1602 | 1603 | // One indentation level less for "switch ... case ... default" 1604 | if ( 1605 | isset( $tokens[$key+1] ) and 1606 | is_array( $tokens[$key+1] ) and ( 1607 | $tokens[$key+1][0] === T_CASE or 1608 | $tokens[$key+1][0] === T_DEFAULT or ( 1609 | isset( $tokens[$key+2] ) and 1610 | is_array( $tokens[$key+2] ) and ( 1611 | $tokens[$key+2][0] === T_CASE or 1612 | $tokens[$key+2][0] === T_DEFAULT 1613 | ) and 1614 | // T_WHITESPACE without \n first 1615 | $tokens[$key+1][0] === T_WHITESPACE and 1616 | strpos( $tokens[$key+1][1], "\n" )===false 1617 | ) 1618 | ) 1619 | ) --$indent; 1620 | 1621 | // One indentation level less for an opening curly brace on a seperate line 1622 | if ( 1623 | isset( $tokens[$key+2] ) and ( 1624 | $tokens[$key+1] === "{" or ( 1625 | is_array( $tokens[$key+1] ) and ( 1626 | $tokens[$key+1][0] === T_CURLY_OPEN or 1627 | $tokens[$key+1][0] === T_DOLLAR_OPEN_CURLY_BRACES 1628 | ) 1629 | ) 1630 | ) and ( 1631 | is_array( $tokens[$key+2] ) and 1632 | $tokens[$key+2][0] === T_WHITESPACE and 1633 | strpos( $tokens[$key+2][1], "\n" )!==false 1634 | ) and ( 1635 | // Only if the curly brace belongs to a control structure 1636 | $control_structure[$curly_braces_count] > 0 1637 | ) 1638 | ) --$indent; 1639 | 1640 | // One additional indentation level for operators at the beginning or the end of a line 1641 | if ( !$round_braces_count ) { 1642 | 1643 | static $operators = array( 1644 | // arithmetic 1645 | "+", 1646 | "-", 1647 | "*", 1648 | "/", 1649 | "%", 1650 | // assignment 1651 | "=", 1652 | array( T_PLUS_EQUAL, "+=" ), 1653 | array( T_MINUS_EQUAL, "-=" ), 1654 | array( T_MUL_EQUAL, "*=" ), 1655 | array( T_DIV_EQUAL, "/=" ), 1656 | array( T_MOD_EQUAL, "%=" ), 1657 | array( T_AND_EQUAL, "&=" ), 1658 | array( T_OR_EQUAL, "|=" ), 1659 | array( T_XOR_EQUAL, "^=" ), 1660 | // bitwise 1661 | "&", 1662 | "|", 1663 | "^", 1664 | array( T_SL, "<<" ), 1665 | array( T_SR, ">>" ), 1666 | // comparison 1667 | array( T_IS_EQUAL, "==" ), 1668 | array( T_IS_IDENTICAL, "===" ), 1669 | array( T_IS_NOT_EQUAL, "!=" ), 1670 | array( T_IS_NOT_EQUAL, "<>" ), 1671 | array( T_IS_NOT_IDENTICAL, "!==" ), 1672 | "<", 1673 | ">", 1674 | array( T_IS_SMALLER_OR_EQUAL, "<=" ), 1675 | array( T_IS_GREATER_OR_EQUAL, ">=" ), 1676 | // logical 1677 | array( T_LOGICAL_AND, "and" ), 1678 | array( T_LOGICAL_OR, "or" ), 1679 | array( T_LOGICAL_XOR, "xor" ), 1680 | array( T_BOOLEAN_AND, "&&" ), 1681 | array( T_BOOLEAN_OR, "||" ), 1682 | // string 1683 | ".", 1684 | array( T_CONCAT_EQUAL, ".=" ), 1685 | // type 1686 | array( T_INSTANCEOF, "instanceof" ) 1687 | ); 1688 | 1689 | if ( 1690 | ( isset( $tokens[$key+1] ) and in_array( $tokens[$key+1], $operators ) ) or 1691 | ( isset( $tokens[$key-1] ) and in_array( $tokens[$key-1], $operators ) ) 1692 | ) { 1693 | ++$indent; 1694 | } elseif ( 1695 | ( isset( $tokens[$key+1] ) and $tokens[$key+1] === "?" ) or 1696 | ( isset( $tokens[$key-1] ) and $tokens[$key-1] === "?" ) 1697 | ) { 1698 | ++$indent; 1699 | $trinity_started = true; 1700 | } elseif ( 1701 | $trinity_started and ( 1702 | ( isset( $tokens[$key+1] ) and $tokens[$key+1] === ":" ) or 1703 | ( isset( $tokens[$key-1] ) and $tokens[$key-1] === ":" ) 1704 | ) 1705 | ) { 1706 | ++$indent; 1707 | $trinity_started = false; 1708 | } 1709 | 1710 | } 1711 | 1712 | $indent_str = str_repeat( $GLOBALS['indent_char'], max( $indent, 0 ) ); 1713 | 1714 | // Indent the current token 1715 | $text = preg_replace( 1716 | "/\n[ \t]*/", 1717 | "\n".$indent_str.( $docblock?" ":"" ), 1718 | $text 1719 | ); 1720 | 1721 | // Cut the indenting at the beginning of the next token 1722 | 1723 | // End of file reached 1724 | if ( !isset( $tokens[$key+1] ) ) return; 1725 | 1726 | if ( is_string( $tokens[$key+1] ) ) { 1727 | $text2 =& $tokens[$key+1]; 1728 | } else { 1729 | $text2 =& $tokens[$key+1][1]; 1730 | } 1731 | 1732 | // Remove indenting at beginning of the the next token 1733 | $text2 = preg_replace( 1734 | "/^[ \t]*/", 1735 | "", 1736 | $text2 1737 | ); 1738 | 1739 | } 1740 | 1741 | 1742 | /** 1743 | * Strips indenting before single closing PHP tags 1744 | * 1745 | * @param array $tokens (reference) 1746 | */ 1747 | function strip_closetag_indenting( &$tokens ) { 1748 | 1749 | foreach ( $tokens as $key => &$token ) { 1750 | if ( is_string( $token ) ) continue; 1751 | if ( 1752 | // T_CLOSE_TAG with following \n 1753 | $token[0] === T_CLOSE_TAG and 1754 | substr( $token[1], -1 ) === "\n" 1755 | ) { 1756 | if ( 1757 | // T_WHITESPACE or T_COMMENT before with \n at the end 1758 | isset( $tokens[$key-1] ) and 1759 | is_array( $tokens[$key-1] ) and 1760 | ( $tokens[$key-1][0] === T_WHITESPACE or $tokens[$key-1][0] === T_COMMENT ) and 1761 | preg_match( "/\n[ \t]*$/", $tokens[$key-1][1] ) 1762 | ) { 1763 | $tokens[$key-1][1] = preg_replace( "/\n[ \t]*$/", "\n", $tokens[$key-1][1] ); 1764 | } elseif ( 1765 | // T_WHITESPACE before without \n 1766 | isset( $tokens[$key-1] ) and 1767 | is_array( $tokens[$key-1] ) and 1768 | $tokens[$key-1][0] === T_WHITESPACE and 1769 | strpos( $tokens[$key-1][1], "\n" )===false and 1770 | // T_WHITESPACE before or T_COMMENT with \n at the end 1771 | isset( $tokens[$key-2] ) and 1772 | is_array( $tokens[$key-2] ) and 1773 | ( $tokens[$key-2][0] === T_WHITESPACE or $tokens[$key-2][0] === T_COMMENT ) and 1774 | preg_match( "/\n[ \t]*$/", $tokens[$key-2][1] ) 1775 | ) { 1776 | $tokens[$key-1] = ""; 1777 | $tokens[$key-2][1] = preg_replace( "/\n[ \t]*$/", "\n", $tokens[$key-2][1] ); 1778 | } 1779 | } 1780 | } 1781 | 1782 | } 1783 | 1784 | 1785 | //////////////// PHPDOC FUNCTIONS /////////////////// 1786 | 1787 | 1788 | /** 1789 | * Gets all defined functions 1790 | * 1791 | * Functions inside of curly braces will be ignored. 1792 | * 1793 | * @param string $content 1794 | * @return array 1795 | */ 1796 | function get_functions( &$content ) { 1797 | 1798 | $tokens = get_tokens( $content ); 1799 | 1800 | $functions = array(); 1801 | $curly_braces_count = 0; 1802 | foreach ( $tokens as $key => &$token ) { 1803 | 1804 | if ( is_string( $token ) ) { 1805 | if ( $token === "{" ) ++$curly_braces_count; 1806 | elseif ( $token === "}" ) --$curly_braces_count; 1807 | } elseif ( 1808 | $token[0] === T_FUNCTION and 1809 | $curly_braces_count === 0 and 1810 | isset( $tokens[$key+2] ) and 1811 | is_array( $tokens[$key+2] ) 1812 | ) { 1813 | $functions[] = $tokens[$key+2][1]; 1814 | } 1815 | 1816 | } 1817 | 1818 | return $functions; 1819 | } 1820 | 1821 | 1822 | /** 1823 | * Gets all defined includes 1824 | * 1825 | * @param array $seetags (reference) 1826 | * @param string $content 1827 | * @param string $file 1828 | */ 1829 | function find_includes( &$seetags, &$content, $file ) { 1830 | 1831 | $tokens = get_tokens( $content ); 1832 | 1833 | foreach ( $tokens as $key => &$token ) { 1834 | if ( is_string( $token ) ) continue; 1835 | 1836 | if ( !in_array( $token[0], array( T_REQUIRE, T_REQUIRE_ONCE, T_INCLUDE, T_INCLUDE_ONCE ) ) ) continue; 1837 | 1838 | $t = get_argument_tokens( $tokens, $key ); 1839 | strip_whitespace( $t ); 1840 | 1841 | // Strip round brackets 1842 | if ( $t[0] === "(" and $t[count( $t )-1] === ")" ) { 1843 | $t = array_splice( $t, 1, -1 ); 1844 | } 1845 | 1846 | if ( !$t ) { 1847 | possible_syntax_error( $tokens, $key, "Missing argument" ); 1848 | continue; 1849 | } 1850 | 1851 | if ( !is_array( $t[0] ) ) continue; 1852 | 1853 | // Strip leading docroot variable or constant 1854 | if ( 1855 | ( $t[0][0] === T_VARIABLE or $t[0][0] === T_STRING ) and 1856 | in_array( $t[0][1], $GLOBALS['docrootvars'] ) and 1857 | $t[1] === "." 1858 | ) { 1859 | $t = array_splice( $t, 2 ); 1860 | } 1861 | 1862 | if ( 1863 | count( $t ) == 1 and 1864 | $t[0][0] === T_CONSTANT_ENCAPSED_STRING 1865 | ) { 1866 | $includedfile = substr( $t[0][1], 1, -1 ); 1867 | $seetags[$includedfile][] = array( $file ); 1868 | continue; 1869 | } 1870 | 1871 | if ( !$t ) { 1872 | possible_syntax_error( $tokens, $key, "String concatenator without following string" ); 1873 | } 1874 | 1875 | } 1876 | 1877 | } 1878 | 1879 | 1880 | /** 1881 | * Replaces one DocTag in a DocBlock 1882 | * 1883 | * Existing valid DocTags will be used without change 1884 | * 1885 | * @param string $text Content of the DocBlock 1886 | * @param string $tagname Name of the tag 1887 | * @param array $tags All tags to be inserted 1888 | * @return string 1889 | */ 1890 | function add_doctags_to_doc_comment( $text, $tagname, $tags ) { 1891 | 1892 | if ( !count( $tags ) ) return $text; 1893 | 1894 | // Replacement for array_unique() 1895 | $tagids = array(); 1896 | foreach ( $tags as $key => $tag ) { 1897 | if ( !in_array( $tag[0], $tagids ) ) { 1898 | $tagids[] = $tag[0]; 1899 | } else { 1900 | unset( $tags[$key] ); 1901 | } 1902 | } 1903 | 1904 | $oldtags = array(); 1905 | 1906 | $lines = explode( "\n", $text ); 1907 | 1908 | $newtext = ""; 1909 | foreach ( $lines as $key => $line ) { 1910 | 1911 | // Add doctags after the last line 1912 | if ( $key == count( $lines )-1 ) { 1913 | foreach ( $tags as $tag ) { 1914 | $tagid = $tag[0]; 1915 | if ( isset( $oldtags[$tagid] ) and count( $oldtags[$tagid] ) ) { 1916 | 1917 | // Use existing line 1918 | foreach ( $oldtags[$tagid] as $oldtag ) { 1919 | 1920 | if ( 1921 | $tagname == "param" and 1922 | preg_match( '/^\s*\*\s+@param\s+([A-Za-z0-9_]+)\s+(\$[A-Za-z0-9_]+)\s+(.*)$/', $oldtag, $matches ) 1923 | ) { 1924 | 1925 | // Replace param type if a type hint exists 1926 | if ( empty( $tag[1] ) ) $tag[1] = $matches[1]; 1927 | 1928 | // Add comment for optional and reference if not already existing 1929 | if ( 1930 | isset( $tag[2] ) and 1931 | substr( $matches[3], 0, strlen( $tag[2] ) ) != $tag[2] 1932 | ) { 1933 | $matches[3] = $tag[2]." ".$matches[3]; 1934 | } 1935 | 1936 | $newtext .= "* @param ".$tag[1]." ".$tag[0]." ".$matches[3]."\n"; 1937 | 1938 | } else { 1939 | // Take old line without changes 1940 | $newtext .= $oldtag."\n"; 1941 | } 1942 | 1943 | } 1944 | 1945 | } else { 1946 | 1947 | // Add new line 1948 | switch ( $tagname ) { 1949 | case "param": 1950 | if ( empty( $tag[1] ) ) $tag[1] = "unknown"; 1951 | $newtext .= "* @param ".$tag[1]." ".$tag[0].( isset( $tag[2] )?" ".$tag[2]:"" )."\n"; 1952 | break; 1953 | case "uses": 1954 | $newtext .= "* @uses ".$tag[0]."()\n"; 1955 | break; 1956 | case "return": 1957 | $newtext .= "* @return unknown\n"; 1958 | break; 1959 | case "author": 1960 | if ( $GLOBALS['default_author'] ) { 1961 | $newtext .= "* @author ".$GLOBALS['default_author']."\n"; 1962 | } 1963 | break; 1964 | case "package": 1965 | $newtext .= "* @package ".$GLOBALS['default_package']."\n"; 1966 | break; 1967 | case "see": 1968 | $newtext .= "* @see ".$tag[0]."\n"; 1969 | break; 1970 | } 1971 | 1972 | } 1973 | 1974 | } 1975 | } 1976 | 1977 | // Match DocTag 1978 | $regex = '^\s*\*\s+@'.$tagname; 1979 | // Match param tag variable 1980 | if ( $tagname=="param" ) $regex .= '[^\$]*(\$[A-Za-z0-9_]+)'; 1981 | if ( preg_match( '/'.$regex.'/', $line, $matches ) ) { 1982 | if ( $tagname=="param" ) $oldtags[$matches[1]][] = $line; 1983 | else $oldtags[""][] = $line; 1984 | } else { 1985 | // Don't change lines without a DocTag 1986 | $newtext .= $line; 1987 | // Add a line break after every line except the last 1988 | if ( $key != count( $lines )-1 ) $newtext.="\n"; 1989 | } 1990 | 1991 | } 1992 | 1993 | return $newtext; 1994 | } 1995 | 1996 | 1997 | /** 1998 | * Collects the doctags for a function docblock 1999 | * 2000 | * @param array $tokens (reference) 2001 | * @return array 2002 | */ 2003 | function collect_doctags( &$tokens ) { 2004 | 2005 | $function_declarations = array(); 2006 | $function = ""; 2007 | $curly_braces_count = 0; 2008 | 2009 | $usestags = array(); 2010 | $paramtags = array(); 2011 | $returntags = array(); 2012 | 2013 | foreach ( $tokens as $key => &$token ) { 2014 | 2015 | if ( is_string( $token ) ) { 2016 | 2017 | if ( $token === "{" ) { 2018 | ++$curly_braces_count; 2019 | } elseif ( $token === "}" ) { 2020 | if ( --$curly_braces_count==0 ) $function = ""; 2021 | } 2022 | 2023 | } else { 2024 | 2025 | switch ( $token[0] ) { 2026 | case T_FUNCTION: 2027 | // Find function definitions 2028 | 2029 | $round_braces_count = 0; 2030 | 2031 | $k = $key + 1; 2032 | 2033 | if ( is_string( $tokens[$k] ) or $tokens[$k][0] !== T_WHITESPACE ) { 2034 | possible_syntax_error( $tokens, $k, "No whitespace found between function keyword and function name" ); 2035 | break; 2036 | } 2037 | 2038 | ++$k; 2039 | 2040 | // & before function name 2041 | if ( $tokens[$k] === "&" ) ++$k; 2042 | 2043 | if ( is_string( $tokens[$k] ) or $tokens[$k][0] !== T_STRING ) { 2044 | possible_syntax_error( $tokens, $k, "No string for function name found" ); 2045 | break; 2046 | } 2047 | 2048 | $function = $tokens[$k][1]; 2049 | $function_declarations[] = $key; 2050 | 2051 | // Collect param-doctags 2052 | $k += 2; 2053 | // Area between round brackets 2054 | $reference = false; 2055 | while ( ( $tokens[$k] != ")" or $round_braces_count ) and $k < count( $tokens ) ) { 2056 | if ( is_string( $tokens[$k] ) ) { 2057 | if ( $tokens[$k] === "(" ) ++$round_braces_count; 2058 | elseif ( $tokens[$k] === ")" ) --$round_braces_count; 2059 | elseif ( $tokens[$k] === "&" ) $reference = true; 2060 | } else { 2061 | $typehint = false; 2062 | if ( 2063 | $tokens[$k][0] === T_VARIABLE 2064 | ) { 2065 | $typehint = ""; 2066 | } elseif ( 2067 | $tokens[$k][0] === T_ARRAY and 2068 | isset( $tokens[$k+1][0] ) and $tokens[$k+1][0] === T_WHITESPACE and 2069 | isset( $tokens[$k+2][0] ) and $tokens[$k+2][0] === T_VARIABLE 2070 | ) { 2071 | $k += 2; 2072 | $typehint = "array"; 2073 | } elseif ( 2074 | $tokens[$k][0] === T_STRING and 2075 | isset( $tokens[$k+1][0] ) and $tokens[$k+1][0] === T_WHITESPACE and 2076 | isset( $tokens[$k+2][0] ) and $tokens[$k+2][0] === T_VARIABLE 2077 | ) { 2078 | $k += 2; 2079 | $typehint = "object"; 2080 | } 2081 | if ( $typehint !== false ) { 2082 | $comments = array(); 2083 | if ( 2084 | ( isset( $tokens[$k+1] ) and $tokens[$k+1] === "=" ) or ( 2085 | isset( $tokens[$k+1][0] ) and $tokens[$k+1][0] === T_WHITESPACE and 2086 | isset( $tokens[$k+2] ) and $tokens[$k+2] === "=" 2087 | ) 2088 | ) { 2089 | $comments[] = "optional"; 2090 | } 2091 | if ( $reference ) { 2092 | $comments[] = "reference"; 2093 | $reference = false; 2094 | } 2095 | if ( count( $comments ) ) { 2096 | $comment = "(".join( ", ", $comments ).")"; 2097 | } else { 2098 | $comment = ""; 2099 | } 2100 | $paramtags[$function][] = array( $tokens[$k][1], $typehint, $comment ); 2101 | } 2102 | } 2103 | ++$k; 2104 | } 2105 | break; 2106 | case T_CURLY_OPEN: 2107 | case T_DOLLAR_OPEN_CURLY_BRACES: 2108 | ++$curly_braces_count; 2109 | break; 2110 | case T_STRING: 2111 | // Find function calls 2112 | if ( 2113 | $tokens[$key+1] === "(" and 2114 | !in_array( $key-2, $function_declarations ) and 2115 | in_array( $token[1], $GLOBALS['functions'] ) 2116 | ) { 2117 | $usestags[$function][] = array( $token[1] ); 2118 | } 2119 | break; 2120 | case T_RETURN: 2121 | // Find returns 2122 | if ( 2123 | $tokens[$key+1] != ";" and 2124 | $tokens[$key+2] != ";" 2125 | ) { 2126 | $returntags[$function][] = array( "" ); 2127 | } 2128 | break; 2129 | } 2130 | 2131 | } 2132 | 2133 | } 2134 | 2135 | return array( $usestags, $paramtags, $returntags ); 2136 | } 2137 | 2138 | 2139 | /** 2140 | * Adds file DocBlocks where missing 2141 | * 2142 | * @param array $tokens (reference) 2143 | */ 2144 | function add_file_docblock( &$tokens ) { 2145 | 2146 | $default_file_docblock = "/**\n". 2147 | " * ".$GLOBALS['file']."\n". 2148 | " *\n"; 2149 | if ( $GLOBALS['default_author'] ) { 2150 | $default_file_docblock .= " * @author ".$GLOBALS['default_author']."\n"; 2151 | } 2152 | $default_file_docblock .= " * @package ".$GLOBALS['default_package']."\n". 2153 | " */"; 2154 | 2155 | // File begins with PHP 2156 | switch ( $tokens[0][0] ) { 2157 | case T_OPEN_TAG: 2158 | 2159 | if ( $GLOBALS['open_tag']=="\n" ) 2214 | ) ); 2215 | } else { 2216 | array_splice( $tokens, 0, 0, array( 2217 | array( T_OPEN_TAG, $GLOBALS['open_tag']."\n" ), 2218 | array( T_DOC_COMMENT, $default_file_docblock ), 2219 | array( T_WHITESPACE, "\n\n\n" ), 2220 | array( T_CLOSE_TAG, "?>\n" ) 2221 | ) ); 2222 | } 2223 | 2224 | } 2225 | 2226 | } 2227 | 2228 | } 2229 | 2230 | 2231 | /** 2232 | * Adds funktion DocBlocks where missing 2233 | * 2234 | * @param array $tokens (reference) 2235 | */ 2236 | function add_function_docblocks( &$tokens ) { 2237 | 2238 | foreach ( $tokens as $key => &$token ) { 2239 | 2240 | if ( is_string( $token ) or $token[0] !== T_FUNCTION ) continue; 2241 | 2242 | // Find beginning of the function declaration 2243 | $k = $key; 2244 | while ( 2245 | isset( $tokens[$k-1] ) and 2246 | strpos( token_text( $tokens[$k-1] ), "\n" )===false 2247 | ) --$k; 2248 | 2249 | if ( 2250 | !isset( $tokens[$k-2] ) or 2251 | !is_array( $tokens[$k-2] ) or 2252 | $tokens[$k-2][0] != T_DOC_COMMENT 2253 | ) { 2254 | 2255 | // Collect old non-phpdoc comments 2256 | $comment = ""; 2257 | $replace = 0; 2258 | while ( 2259 | isset( $tokens[$k-1] ) and 2260 | is_array( $tokens[$k-1] ) and 2261 | $tokens[$k-1][0] === T_COMMENT 2262 | ) { 2263 | $comment = " * ".trim( ltrim( trim( $tokens[$k-1][1] ), "/#" ) )."\n".$comment; 2264 | --$k; 2265 | ++$replace; 2266 | } 2267 | 2268 | if ( !$comment ) $comment = " *\n"; 2269 | 2270 | array_splice( $tokens, $k, $replace, array( 2271 | array( T_DOC_COMMENT, "/**\n". 2272 | $comment. 2273 | " */" ), 2274 | array( T_WHITESPACE, "\n" ) 2275 | ) ); 2276 | 2277 | } 2278 | 2279 | } 2280 | 2281 | } 2282 | 2283 | 2284 | /** 2285 | * Adds DocTags to file or function DocBlocks 2286 | * 2287 | * @param array $tokens (reference) 2288 | * @param array $usetags 2289 | * @param array $paramtags 2290 | * @param array $returntags 2291 | * @param array $seetags 2292 | */ 2293 | function add_doctags( &$tokens, $usetags, $paramtags, $returntags, $seetags ) { 2294 | 2295 | $filedocblock = false; 2296 | 2297 | foreach ( $tokens as $key => &$token ) { 2298 | 2299 | if ( is_string( $token ) ) continue; 2300 | list( $id, $text ) = $token; 2301 | if ( $id != T_DOC_COMMENT ) continue; 2302 | 2303 | $k = $key + 1; 2304 | while ( in_array( $tokens[$k][0], array( T_WHITESPACE, T_STATIC, T_PUBLIC, T_PROTECTED, T_PRIVATE ) ) ) ++$k; 2305 | 2306 | if ( 2307 | $tokens[$k][0] === T_FUNCTION and 2308 | $tokens[$k+1][0] === T_WHITESPACE and 2309 | $tokens[$k+2][0] === T_STRING 2310 | ) { 2311 | 2312 | // Function DocBlock 2313 | $f = $tokens[$k+2][1]; 2314 | if ( isset( $paramtags[$f] ) ) { 2315 | $tokens[$key] = array( $id, add_doctags_to_doc_comment( $tokens[$key][1], "param", $paramtags[$f] ) ); 2316 | } 2317 | if ( isset( $returntags[$f] ) ) { 2318 | $tokens[$key] = array( $id, add_doctags_to_doc_comment( $tokens[$key][1], "return", $returntags[$f] ) ); 2319 | } 2320 | if ( isset( $usestags[$f] ) ) { 2321 | $tokens[$key] = array( $id, add_doctags_to_doc_comment( $tokens[$key][1], "uses", $usestags[$f] ) ); 2322 | } 2323 | 2324 | } elseif ( !$filedocblock ) { 2325 | 2326 | // File DocBlock 2327 | if ( isset( $usestags[""] ) ) { 2328 | $tokens[$key] = array( $id, add_doctags_to_doc_comment( $tokens[$key][1], "uses", $usestags[""] ) ); 2329 | } 2330 | $tokens[$key] = array( $id, add_doctags_to_doc_comment( $tokens[$key][1], "author", array( array( "" ) ) ) ); 2331 | $tokens[$key] = array( $id, add_doctags_to_doc_comment( $tokens[$key][1], "package", array( array( "" ) ) ) ); 2332 | if ( isset( $seetags[$GLOBALS['file']] ) ) { 2333 | $tokens[$key] = array( $id, add_doctags_to_doc_comment( $tokens[$key][1], "see", $seetags[$GLOBALS['file']] ) ); 2334 | } 2335 | 2336 | } 2337 | 2338 | $filedocblock = true; 2339 | 2340 | } 2341 | 2342 | } 2343 | --------------------------------------------------------------------------------