├── .gitignore ├── src ├── ansi_escape_constants.php ├── PhpDocumentor.php ├── functions.php ├── MarkdownTable.php ├── Printer.php ├── CommentParser.php └── WPDocGen.php ├── bin └── wp-doc-gen.php ├── CHANGELOG.md ├── examples ├── hooks.php ├── hooks.md ├── shortcodes.md └── shortcodes.php ├── composer.lock ├── .github └── FUNDING.yml ├── composer.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | /vendor/ -------------------------------------------------------------------------------- /src/ansi_escape_constants.php: -------------------------------------------------------------------------------- 1 | init(); 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | [2.0.2] 2 | ### Added 3 | - Added support for markdown headers 4 | - Improved parser to support variable names like $this->method 5 | - Minor changes 6 | 7 | [2.0.0] 8 | ### Added 9 | - Added option -s or --shortcode to search for add_shortcode functions 10 | - Added option -h or --help to print a help message 11 | - Added option -v or --verbose to print detailed output 12 | - Added option -V or --version to print the version number 13 | - The script now prints colors to improve readability. 14 | - Added files into examples dir 15 | -------------------------------------------------------------------------------- /examples/hooks.php: -------------------------------------------------------------------------------- 1 | =8.0" 17 | }, 18 | "platform-dev": [], 19 | "plugin-api-version": "2.3.0" 20 | } 21 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: dudo1985 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: dario_curvino 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ["https://www.paypal.com/donate/?hosted_button_id=SVTAVUF62QZ4W"] 14 | -------------------------------------------------------------------------------- /examples/hooks.md: -------------------------------------------------------------------------------- 1 | 2 | ### `do_action('yasr_add_settings_tab')` 3 | 4 | Source: [examples/hooks.php, line 12](examples/hooks.php:12) 5 | 6 | _Hook here to add new settings tab_ 7 | 8 | _This is the second line_ 9 | 10 | |Argument | Type | Description | 11 | | --- | --- | --- | 12 | |$foo | string | This is the foo variable | 13 | ___ 14 | ### `do_action('yasr_right_settings_panel_box')` 15 | 16 | Source: [examples/hooks.php, line 19](examples/hooks.php:19) 17 | 18 | _Hook here to add new settings tab_ 19 | 20 | |Argument | Type | Description | 21 | | --- | --- | --- | 22 | |$int | int | an int, it is 5 | 23 | ___ 24 | ### `apply_filters('yasr_vv_cookie')` 25 | 26 | Source: [examples/hooks.php, line 24](examples/hooks.php:24) 27 | 28 | _customize visitor_votes cookie name, no param given just the description_ 29 | 30 | ___ 31 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dudo1985/wpdocgen", 3 | "description": "Documentation Generator for WordPress.", 4 | "keywords": ["documentation", "hooks", "phpdoc", "wordpress", "markdown"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Dario Curvino", 9 | "email": "dario.yasr@gmail.com", 10 | "homepage": "https://dariocurvino.it" 11 | } 12 | ], 13 | "autoload": { 14 | "psr-4": { 15 | "Dudo1985\\WPDocGen\\": "src", 16 | "": "src" 17 | } 18 | }, 19 | "bin": [ 20 | "bin/wp-doc-gen.php" 21 | ], 22 | "require": { 23 | "php": ">=8.0" 24 | }, 25 | "scripts": { 26 | "test": [ 27 | "php bin/wp-doc-gen.php examples examples/hooks.md --prefix yasr_", 28 | "php bin/wp-doc-gen.php examples examples/shortcodes.md --prefix yasr_ --shortcode" 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Dario Curvino 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/PhpDocumentor.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | */ 7 | namespace Dudo1985\WPDocGen; 8 | 9 | if (!class_exists('Dudo1985\WPDocGen\PhpDocumentor')) { 10 | 11 | class PhpDocumentor { 12 | 13 | /** 14 | * @return array 15 | * @since 2.0.2 16 | * @author Dario Curvino <@dudo> 17 | * 18 | */ 19 | public static function returnTags(): array { 20 | return array( 21 | '@abstract', 22 | '@access', 23 | '@author', 24 | '@category', 25 | '@copyright', 26 | '@deprecated', 27 | '@example', 28 | '@final', 29 | '@filesource', 30 | '@global', 31 | '@ignore', 32 | '@internal', 33 | '@license', 34 | '@link', 35 | '@method', 36 | '@name', 37 | '@package', 38 | '@param', 39 | '@property', 40 | '@return', 41 | '@see', 42 | '@since', 43 | '@static', 44 | '@staticvar', 45 | '@subpackage', 46 | '@todo', 47 | '@tutorial', 48 | '@uses', 49 | '@var', 50 | '@version' 51 | ); 52 | } 53 | 54 | /** 55 | * Return true if the given string is a phpDocTag, false otherwise 56 | * 57 | * @param $string 58 | * @return bool 59 | * @author Dario Curvino <@dudo> 60 | * 61 | * @since 2.0.2 62 | */ 63 | public static function isTag($string): bool { 64 | if (in_array($string, self::returnTags(), true)) { 65 | return true; 66 | } 67 | return false; 68 | } 69 | } 70 | 71 | } //end class 72 | -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | 9 | * @since 2.0.3 10 | * 11 | * @param $line 12 | * 13 | * @return bool 14 | */ 15 | function is_header ($line):bool { 16 | if(str_starts_with($line, '#')) { 17 | return true; 18 | } 19 | return false; 20 | } 21 | 22 | /** 23 | * Check if the provided word is an argument 24 | * 25 | * @param $word 26 | * 27 | * @return bool 28 | * @author Dario Curvino <@dudo> 29 | * @since 1.0.0 30 | * 31 | */ 32 | function is_argument($word): bool { 33 | if (str_starts_with($word, '$')) { 34 | return true; 35 | } 36 | return false; 37 | } 38 | 39 | /** 40 | * Check if the provided string begin with a tag 41 | * 42 | * @author Dario Curvino <@dudo> 43 | * 44 | * @param $string 45 | * 46 | * @since 1.0.0 47 | *@return bool 48 | */ 49 | function is_tag($string): bool { 50 | if (str_starts_with($string, '@')) { 51 | $possible_tag = find_first_word($string); 52 | if(PhpDocumentor::isTag($possible_tag) === true) { 53 | return true; 54 | } 55 | } 56 | return false; 57 | } 58 | 59 | /** 60 | * Return the first word of a string 61 | * 62 | * @param $string 63 | * 64 | * @return false|string 65 | * @author Dario Curvino <@dudo> 66 | * @since 1.0.0 67 | * 68 | */ 69 | function find_first_word($string): bool|string { 70 | return strtok($string, ' '); // First word of the string 71 | } 72 | 73 | 74 | /** 75 | * If a string begin with a provided char, remove it 76 | * 77 | * @author Dario Curvino <@dudo> 78 | * @since 2.0.2 79 | * 80 | * @param $string 81 | * @param $char | The char to remove 82 | * 83 | * @return string|null 84 | */ 85 | function remove_char_begin_string ($string, $char): string|null { 86 | //remove all the * at the beginning of the string, if exists 87 | if (str_starts_with($string, $char)) { 88 | return trim(substr($string, 1)); 89 | } 90 | return null; 91 | } -------------------------------------------------------------------------------- /src/MarkdownTable.php: -------------------------------------------------------------------------------- 1 | 33 | * @since 1.0.0 34 | * 35 | * @param string|array $headers 36 | * 37 | * @return void 38 | */ 39 | public function addHeader(string|array $headers): void { 40 | if (is_array($headers)) { 41 | foreach ($headers as $header) { 42 | $this->headers[] = $header; 43 | } 44 | } 45 | else { 46 | $this->headers[] = $headers; 47 | } 48 | } 49 | 50 | /** 51 | * This method is used to add a row to the Markdown table. It accepts a string or an array of strings as an argument. 52 | * 53 | * @author Dario Curvino <@dudo> 54 | * @since 1.0.0 55 | * 56 | * @param string|array $row 57 | * 58 | * @return void 59 | */ 60 | public function addRow(string|array $row): void { 61 | $this->rows[] = $row; 62 | } 63 | 64 | /** 65 | * This method is used to generate the Markdown table. It returns a string that contains the entire markdown table. 66 | * 67 | * @author Dario Curvino <@dudo> 68 | * @since 1.0.0 69 | * @return string 70 | */ 71 | public function getTable(): string { 72 | // Add headers to table 73 | $table = '|' . implode(' | ', $this->headers) . " |\n"; 74 | 75 | // Add separator row to table 76 | $table .= '|' . str_repeat(' --- |', count($this->headers)) . "\n"; 77 | 78 | // Add rows to table 79 | foreach ($this->rows as $row) { 80 | $table .= '|' . implode(' | ', $row) . " |\n"; 81 | } 82 | 83 | return $table; 84 | } 85 | 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /examples/shortcodes.md: -------------------------------------------------------------------------------- 1 | 2 | ### Shortcode yasr_overall_rating 3 | 4 | Source: [examples/shortcodes.php, line 41](examples/shortcodes.php:41) 5 | 6 | ### How to use it? 7 | 8 | _To insert the rating in this widget, there are two ways:_ 9 | 10 | _- If you're using the Classic editor, simply give a rating in the YASR metabox that appears at the top right of the_ 11 | 12 | _screen while you're writing a new post or page._ 13 | 14 | _- If you're using the new Gutenberg editor, click on the "+" icon to add a block, search for YASR, and select_ 15 | 16 | _YASR: Overall Rating. A new panel will appear to the right, where you can add your rating._ 17 | 18 | ___ 19 | ### Shortcode yasr_visitor_votes 20 | 21 | Source: [examples/shortcodes.php, line 60](examples/shortcodes.php:60) 22 | 23 | ### How to use it? 24 | 25 | _To insert the rating in this widget, there are two ways:_ 26 | 27 | _- You can paste the shortcode [yasr_visitor_votes] where you need to show the widget, or you can use the auto insert_ 28 | 29 | _feature as explained in [this tutorial](https://yetanotherstarsrating.com/tutorials/)._ 30 | 31 | _- If you're using the new Gutenberg editor, click on the "+" icon to add a block, search for YASR and select YASR: Visitor Votes._ 32 | 33 | ___ 34 | ### Shortcode yasr_multiset 35 | 36 | Source: [examples/shortcodes.php, line 69](examples/shortcodes.php:69) 37 | 38 | _`[yasr_multiset]` allows you to insert a rating for each aspect of your review (up to nine rows)._ 39 | 40 | _The setid is a number that identifies the multiset._ 41 | 42 | _This shortcode return author multi set_ 43 | 44 | ___ 45 | ### Shortcode yasr_visitor_multiset 46 | 47 | Source: [examples/shortcodes.php, line 74](examples/shortcodes.php:74) 48 | 49 | _Yasr Visitor Multiset_ 50 | 51 | ___ 52 | ### Shortcode yasr_ov_ranking 53 | 54 | Source: [examples/shortcodes.php, line 82](examples/shortcodes.php:82) 55 | 56 | _Yasr Overall Ranking_ 57 | 58 | _This shortcode print the highest rated posts by overall_rating_ 59 | 60 | ___ 61 | ### Shortcode yasr_most_or_highest_rated_posts 62 | 63 | Source: [examples/shortcodes.php, line 89](examples/shortcodes.php:89) 64 | 65 | _Yasr Visitor Votes Ranking_ 66 | 67 | _This shortcode print the higher / most rated posts with yasr_visitor_votes_ 68 | 69 | ___ 70 | ### Shortcode yasr_top_reviewers 71 | 72 | Source: [examples/shortcodes.php, line 98](examples/shortcodes.php:98) 73 | 74 | _Yasr Top reviewers_ 75 | 76 | _Shortcode to display most active reviewers_ 77 | 78 | ___ 79 | ### Shortcode yasr_most_active_users 80 | 81 | Source: [examples/shortcodes.php, line 106](examples/shortcodes.php:106) 82 | 83 | _Yasr Most Active users_ 84 | 85 | _This shortcode show which users leave more votes on yasr_visitor_votes_ 86 | 87 | ___ 88 | ### Shortcode yasr_multi_set_ranking 89 | 90 | Source: [examples/shortcodes.php, line 112](examples/shortcodes.php:112) 91 | 92 | _YASR Multiset Ranking_ 93 | 94 | ___ 95 | ### Shortcode yasr_visitor_multi_set_ranking 96 | 97 | Source: [examples/shortcodes.php, line 117](examples/shortcodes.php:117) 98 | 99 | _Yasr Visitor Multiset Ranking_ 100 | 101 | ___ 102 | ### Shortcode yasr_user_rate_history 103 | 104 | Source: [examples/shortcodes.php, line 124](examples/shortcodes.php:124) 105 | 106 | _Yasr User Rate History_ 107 | 108 | _When a user is logged in, print all the rating that user leaved_ 109 | 110 | ___ 111 | ### Shortcode yasr_display_posts 112 | 113 | Source: [examples/shortcodes.php, line 132](examples/shortcodes.php:132) 114 | 115 | _Yasr Display Posts_ 116 | 117 | _Display your posts according to YASR ratings. This shortcode works only on pages._ 118 | 119 | ___ 120 | -------------------------------------------------------------------------------- /src/Printer.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | namespace Dudo1985\WPDocGen; 10 | 11 | if (!class_exists('Dudo1985\WPDocGen\Printer')) { 12 | 13 | class Printer { 14 | 15 | const FORMAT_FIRST_INDENT = "%-2s"; 16 | 17 | /** 18 | * Print newline char 19 | * 20 | * @param int $num_of_newlines 21 | * @return void 22 | * @author Dario Curvino <@dudo> 23 | * 24 | * @since 2.0.0 25 | */ 26 | public function newline(int $num_of_newlines = 1): void { 27 | if($num_of_newlines < 1) { 28 | $num_of_newlines = 1; 29 | } 30 | if($num_of_newlines > 10) { 31 | $num_of_newlines = 10; 32 | } 33 | 34 | for($i=1; $i <= $num_of_newlines; $i++) { 35 | echo "\n"; 36 | } 37 | } 38 | 39 | /** 40 | * Just do echo with "\n" 41 | * 42 | * @param $message 43 | * @return void 44 | * @author Dario Curvino <@dudo> 45 | * 46 | * @since 1.02 47 | */ 48 | public function message($message): void { 49 | echo $message . "\n"; 50 | } 51 | 52 | /** 53 | * @param $message 54 | * @return void 55 | * @author Dario Curvino <@dudo> 56 | * 57 | * @since 2.0.0 58 | */ 59 | public function error($message): void { 60 | echo ANSI_BOLD . ANSI_RED .' Error:'. ANSI_RESET. ' ' .$message ."\n"; 61 | } 62 | 63 | 64 | /** 65 | * Message + background color for dir name 66 | * 67 | * @param $message 68 | * @param $folder 69 | * @return void 70 | * @since 2.0.0 71 | * @author Dario Curvino <@dudo> 72 | * 73 | */ 74 | public function messageWithBackground($message, $message_in_bg): void { 75 | echo $message . $this->returnStringWithBackground($message_in_bg) ."\n"; 76 | } 77 | 78 | /** 79 | * print green text 80 | * 81 | * @param $message 82 | * @return void 83 | * @author Dario Curvino <@dudo> 84 | * 85 | * @since 2.0.0 86 | */ 87 | public function messageGreen($message): void { 88 | echo ANSI_GREEN . $message . ANSI_RESET ."\n"; 89 | } 90 | 91 | /** 92 | * Echo options row 93 | * 94 | * @param $options 95 | * @param $description 96 | * @return void 97 | * @since 2.0.0 98 | * @author Dario Curvino <@dudo> 99 | * 100 | */ 101 | public function helpOption ($options, $description): void { 102 | $format_options_width = "%-28s"; 103 | $format_description_text = "%s\n"; 104 | 105 | $format = self::FORMAT_FIRST_INDENT . $format_options_width . $format_description_text; 106 | 107 | $options = ANSI_LIGHT_YELLOW . $options . ANSI_RESET; 108 | $description = ANSI_BOLD . $description . ANSI_RESET; 109 | 110 | //print the string, first param is empty space 111 | echo sprintf($format, '', $options , $description); 112 | } 113 | 114 | /** 115 | * Print two lines, the first in italic and the second with nackground 116 | * 117 | * @param $description 118 | * @param $example 119 | * @return void 120 | * @since 2.0.0 121 | * @author Dario Curvino <@dudo> 122 | * 123 | */ 124 | public function helpExamples ($description, $example): void { 125 | $description = WPDocGen::removeMultipleWhitespaces($description); 126 | $description = ANSI_ITALIC. $description. ANSI_RESET; 127 | $example = $this->returnStringWithBackground($example); 128 | 129 | $format = self::FORMAT_FIRST_INDENT ."%s\n" . self::FORMAT_FIRST_INDENT . "%s\n"; 130 | 131 | echo sprintf($format, '', $description, '', $example); 132 | } 133 | 134 | /** 135 | * Add ANSI_BG_DARK_GREY to a string 136 | * 137 | * @param $string 138 | * @return string 139 | * @author Dario Curvino <@dudo> 140 | * 141 | */ 142 | public function returnStringWithBackground ($string): string { 143 | return ANSI_BG_DARK_GREY . ($string) . ANSI_RESET; 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /examples/shortcodes.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | 21 | if (!defined('ABSPATH')) { 22 | exit('You\'re not allowed to see this page'); 23 | } // Exit if accessed directly 24 | 25 | /** 26 | ** 27 | * ###What is? 28 | * `[yasr_overall_rating]` shortcode is read only and is used by the reviewer. 29 | * It comes in three sizes: "Small", "Medium", and "Large". 30 | * The text displayed before or after the rating can be customized in the settings. 31 | * The shortcode can be manually placed or automatically inserted using the auto insert feature 32 | * ### How to use it? 33 | * To insert the rating in this widget, there are two ways: 34 | * - If you're using the Classic editor, simply give a rating in the YASR metabox that appears at the top right of the 35 | * screen while you're writing a new post or page. 36 | * - If you're using the new Gutenberg editor, click on the "+" icon to add a block, search for YASR, and select 37 | * YASR: Overall Rating. A new panel will appear to the right, where you can add your rating. 38 | * 39 | * @return string|void|null 40 | */ 41 | add_shortcode('yasr_overall_rating', 'shortcode_overall_rating_callback'); 42 | 43 | /** 44 | * 45 | * ### What is? 46 | * With `[yasr_visitor_votes]` visitors can rate a post or page. 47 | * With it, you can: 48 | * - Choose to allow anonymous or logged in only users. 49 | * - Logged-in users can update their vote anytime. 50 | * - Size can be “Small”, “Medium” or “Large”. 51 | * - Customize the text shown before or after. 52 | * - Hover on the chart bar icon to see the stats. 53 | * ### How to use it? 54 | * To insert the rating in this widget, there are two ways: 55 | * - You can paste the shortcode [yasr_visitor_votes] where you need to show the widget, or you can use the auto insert 56 | * feature as explained in [this tutorial](https://yetanotherstarsrating.com/tutorials/). 57 | * - If you're using the new Gutenberg editor, click on the "+" icon to add a block, search for YASR and select YASR: Visitor Votes. 58 | * 59 | */ 60 | add_shortcode('yasr_visitor_votes', 'shortcode_visitor_votes_callback'); 61 | 62 | /** 63 | * `[yasr_multiset]` allows you to insert a rating for each aspect of your review (up to nine rows). 64 | * 65 | * The setid is a number that identifies the multiset. 66 | * 67 | * This shortcode return author multi set 68 | */ 69 | add_shortcode ('yasr_multiset', 'yasr_multiset_callback'); 70 | 71 | /** 72 | * Yasr Visitor Multiset 73 | */ 74 | add_shortcode ('yasr_visitor_multiset', 'yasr_visitor_multiset_callback'); 75 | 76 | /** 77 | * Yasr Overall Ranking 78 | * 79 | * This shortcode print the highest rated posts by overall_rating 80 | * @since 2.6.2 81 | */ 82 | add_shortcode ('yasr_ov_ranking', 'yasr_ov_ranking_callback'); 83 | 84 | /** 85 | * Yasr Visitor Votes Ranking 86 | * 87 | * This shortcode print the higher / most rated posts with yasr_visitor_votes 88 | */ 89 | add_shortcode ('yasr_most_or_highest_rated_posts', 'yasr_most_or_highest_rated_posts_callback'); 90 | 91 | 92 | /** 93 | * Yasr Top reviewers 94 | * 95 | * Shortcode to display most active reviewers 96 | * @since 2.6.2 97 | */ 98 | add_shortcode ('yasr_top_reviewers', 'yasr_ranking_users_callback'); 99 | 100 | /** 101 | * Yasr Most Active users 102 | * 103 | * This shortcode show which users leave more votes on yasr_visitor_votes 104 | * @since 2.6.2 105 | */ 106 | add_shortcode ('yasr_most_active_users', 'yasr_ranking_users_callback'); 107 | 108 | 109 | /** 110 | * YASR Multiset Ranking 111 | */ 112 | add_shortcode ('yasr_multi_set_ranking', 'yasr_multi_set_ranking_callback'); 113 | 114 | /** 115 | * Yasr Visitor Multiset Ranking 116 | */ 117 | add_shortcode ('yasr_visitor_multi_set_ranking', 'yasr_visitor_multi_set_ranking_callback'); 118 | 119 | /** 120 | * Yasr User Rate History 121 | * 122 | * When a user is logged in, print all the rating that user leaved 123 | */ 124 | add_shortcode('yasr_user_rate_history', 'yasr_users_front_widget_callback'); 125 | /** 126 | * Yasr Display Posts 127 | * 128 | * Display your posts according to YASR ratings. This shortcode works only on pages. 129 | * 130 | * @since 3.3.0 131 | */ 132 | add_shortcode('yasr_display_posts', 'yasr_display_posts_callback'); 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

WP Doc Generator

2 | 3 | WordPress Doc Generator is a tool to automatically extract data about the __actions__, 4 | __filters__ and __shortcodes__ of your WordPress theme or plugin. 5 | 6 | 7 | ## Table of contents 8 | 9 | - [Getting Started](#getting-started) 10 | - [Command Line Usage](#command-line-usage) 11 | - [Notes](#notes) 12 | - [Alternatives](#alternatives) 13 | - [Links](#links) 14 | 15 | ## Getting Started 16 | 17 | ### Installation 18 | 19 | Install it with composer 20 | 21 | ``` 22 | composer require dudo1985/wpdocgen --dev 23 | ``` 24 | 25 | ## Command Line Usage 26 | 27 | First parameter is the input directory, second is the output file, e.g. 28 | 29 | #### `vendor/bin/wp-doc-gen . hooks.md` 30 | 31 | This will parse all the files in the current directory (.) and write a file called hooks.md 32 | 33 | ### Optional params 34 | 35 | #### `--shortcodes` or `-s` 36 | 37 | By default, WPDocGen search for hooks `apply_filters` and `do_actions`. 38 | However, if you use the `--shortcodes` or `-s` will search for `add_shortcode` function instead. 39 | 40 | #### `vendor/bin/wp-doc-gen . shortcodes.md -s` 41 | 42 | --- 43 | 44 | #### `--exclude` or `-e` 45 | 46 | Exclude the specified folders, e.g. 47 | #### `vendor/bin/wp-doc-gen . hooks.md --exclude vendor node_modules` 48 | 49 | Another example, if you're launching the script from another dir: 50 | #### `vendor bin/wp-doc-gen.php ../my-plugin/ docs/hooks.md --exclude vendor node_modules --prefix yasr` 51 | 52 | There is no need to include the full paths of the excluded dirs, it is automatically ../my-plugin/vendor and 53 | ../my-plugin/node_modules 54 | 55 | --- 56 | 57 | #### `--prefix` or `-p` 58 | Only parse hooks or shortcodes starting with the specified prefixes. 59 | 60 | #### `vendor/bin/wp-doc-gen . hooks.md --exclude vendor node_modules --prefix prefix_1 prefix_2` 61 | 62 | --- 63 | 64 | #### `--verbose` or `-v` 65 | More detailed error messages. 66 | 67 | --- 68 | 69 | #### `--version` or `-V` 70 | Print the version number 71 | 72 | ## Real life Examples 73 | ### Generated markdown files 74 | - https://github.com/Dudo1985/Yet-Another-Stars-Rating/blob/master/docs/yasr_hooks.md 75 | - https://github.com/Dudo1985/Yet-Another-Stars-Rating/blob/master/docs/yasr_shortcodes.md 76 | ### Composer script 77 | - [How I use it into composer](https://github.com/Dudo1985/Yet-Another-Stars-Rating/blob/182b01703f62e3303fe214252ad34cf4c2813005/composer.json#L18) 78 | 79 | ## Notes 80 | To make the parser work fine, the comment must be a valid phpDocBlock, e.g. 81 | 82 | > ``` 83 | > /** 84 | > * Use this action to add tabs inside shortcode creator for tinymce 85 | > */ 86 | > do_action('yasr_add_tabs_on_tinypopupform'); 87 | > ``` 88 | 89 | will generate this code 90 | 91 | >### `do_action('yasr_add_tabs_on_tinypopupform')` 92 | >Source: [../yet-another-stars-rating/admin/editor/YasrEditorHooks.php, line 219](../yet-another-stars-rating/admin/editor//YasrEditorHooks.php:219) 93 | > 94 | >*Use this action to add tabs inside shortcode creator for tinymce* 95 | 96 | or, another example with tags 97 | 98 | > ``` 99 | > /** 100 | > * Use this action to add content inside shortcode creator 101 | > * 102 | > * @param int $n_multi_set 103 | > * @param string $multi_set the multiset name 104 | > */ 105 | > do_action('yasr_add_content_on_tinypopupform', $n_multi_set, $multi_set); 106 | >``` 107 | 108 | will generate this code with table 109 | 110 | > ### `do_action('yasr_add_content_on_tinypopupform')` 111 | > 112 | > Source: [../yet-another-stars-rating/admin/editor/YasrEditorHooks.php, line 235](../yet-another-stars-rating/admin/editor/YasrEditorHooks.php:235) 113 | > 114 | > *Use this action to add content inside shortcode creator* 115 | > ``` 116 | > 117 | > | Argument | Type | Description | 118 | > |--------------|--------|-------------------| 119 | > | $n_multi_set | int | | 120 | > | $multi_set | string | the multiset name | 121 | > ``` 122 | 123 | 124 | But, if you use the type *after* the argument, e.g. 125 | 126 | > 127 | > ``` 128 | > /** 129 | > * @param $n_multi_set int 130 | > */ 131 | > ``` 132 | > 133 | 134 | this will insert the type (*int* and *string* in this example) inside the "Description" column: 135 | 136 | > 137 | > ``` 138 | > | Argument | Type | Description | 139 | > |--------------|------|--------------------------| 140 | > | $n_multi_set | | int | 141 | > ``` 142 | > 143 | 144 | ## Alternatives 145 | Here is a list of alternatives that I found. However, none of these satisfied my needs 146 | 147 | - [WP Documentor](https://github.com/pronamic/wp-documentor/) by [Pronamic](https://github.com/pronamic) 148 | | This is the project that I used for a while, but I needed something to best fit my needs. The following list comes 149 | from their readme 150 | 151 | - [WP Parser](https://github.com/WordPress/phpdoc-parser) by [WordPress](https://github.com/WordPress) 152 | - [Hookster](https://github.com/themeblvd/hookster) by [Theme Blvd](https://github.com/themeblvd) 153 | - [WordPress HookDoc](https://github.com/matzeeable/wp-hookdoc) by [Matthias Günter](https://github.com/matzeeable) 154 | - [GitHub Actions for WordPress](https://github.com/10up/actions-wordpress/blob/stable/hookdocs-workflow.md) by [10up](https://github.com/10up) 155 | - [Yoast Parser](https://github.com/Yoast/code-documentation-extractor) by [Yoast](https://github.com/Yoast) 156 | - [WooCommerce Code Reference Generator](https://github.com/woocommerce/code-reference) by [WooCommerce](https://github.com/woocommerce) 157 | - [WordPress Hooks Reference](https://github.com/johnbillion/wp-hooks) by [John Blackbourn](https://github.com/johnbillion) / [Human Made](https://github.com/humanmade) 158 | - [wp-hooks-generator](https://github.com/johnbillion/wp-hooks-generator) by [John Blackbourn](https://github.com/johnbillion) / [Human Made](https://github.com/humanmade) 159 | 160 | ## Links 161 | 162 | - https://developer.wordpress.org/plugins/hooks/ 163 | - https://developer.wordpress.org/plugins/hooks/actions/ 164 | - https://developer.wordpress.org/reference/functions/do_action/ 165 | - https://developer.wordpress.org/reference/functions/add_action/ 166 | - https://developer.wordpress.org/plugins/hooks/filters/ 167 | - https://developer.wordpress.org/reference/functions/apply_filters/ 168 | - https://developer.wordpress.org/reference/functions/add_filter/ 169 | - https://developer.wordpress.org/reference/hooks/ 170 | - https://developer.wordpress.org/reference/functions/add_shortcode/ 171 | - https://www.phpdoc.org/ 172 | - https://github.com/phpdocumentor/phpdocumentor 173 | -------------------------------------------------------------------------------- /src/CommentParser.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | 8 | namespace Dudo1985\WPDocGen; 9 | 10 | use SplFileObject; 11 | 12 | if (!class_exists('Dudo1985\WPDocGen\CommentParser')) { 13 | 14 | class CommentParser { 15 | /** 16 | * Given a file path and a line number, returns the documentation comment 17 | * preceding the line as a string. 18 | * 19 | * @param string $filePath The path of the file to analyze. 20 | * @param int $lineNumber The line number to analyze. 21 | * 22 | * @return array The documentation comment preceding the line as an array. 23 | */ 24 | function getDocCommentByLine(string $filePath, int $lineNumber): array { 25 | $comment = []; //avoid undefined 26 | $file = new SplFileObject($filePath); 27 | $file->seek($lineNumber - 2); // positions on the previous line 28 | 29 | // Analyzes the previous lines to find the documentation comment 30 | $line = trim($file->current()); 31 | 32 | //if the previous line is the end of the comment, get the text until /** (the begin of the comment) is reached 33 | if ($line === '*/') { 34 | while ($file->valid()) { 35 | $file->seek($file->key() - 1); 36 | $line = trim($file->current()); 37 | 38 | //found the beginning of the comment 39 | if ($line === '/**') { 40 | break; 41 | } 42 | } 43 | } 44 | 45 | // If line is the beginning of the comment, start reading the comment 46 | if ($line === '/**') { 47 | $comment = $this->loopComment($file); 48 | } 49 | 50 | return $comment; 51 | } 52 | 53 | /** 54 | * Loops through a file to extract the comment block starting at the current line, and returns it as an array. 55 | * 56 | * @param $file | SplFileObject object 57 | * 58 | * @return array 59 | * @author Dario Curvino <@dudo> 60 | * @since 1.0.0 61 | * 62 | */ 63 | function loopComment($file): array { 64 | $comment = []; 65 | //in markdown, this is for italics 66 | $comment['description'] = ''; 67 | 68 | while ($file->valid()) { 69 | //trim the current line 70 | $comment_line = trim($file->current()); 71 | 72 | // Go head if this is the begin of the comment or a row starting with * 73 | if ($comment_line === '/**' || $comment_line === '*') { 74 | $file->next(); 75 | continue; 76 | } 77 | 78 | //if this is the end of the comment, exit from cycle 79 | if ($comment_line === '*/') { 80 | break; 81 | } 82 | 83 | //remove all the * at the beginning of the string, if exists 84 | $comment_line = remove_char_begin_string($comment_line, '*'); 85 | 86 | //if the string begins with a header, leave it and go to the next line 87 | if(is_header($comment_line) === true) { 88 | $comment['description'] = $comment_line; 89 | $file->next(); 90 | continue; 91 | } 92 | 93 | if (is_tag($comment_line) !== true) { 94 | $comment['description'] .= $this->writeCommentDescription($comment['description'], $comment_line); 95 | } 96 | //the line begins with a tag 97 | else { 98 | $comment['args'][] = $this->removeTagFromString($comment_line); 99 | } 100 | //go to the next line 101 | $file->next(); 102 | } 103 | return $comment; 104 | } 105 | 106 | /** 107 | * This function will return the new comment line. 108 | * if the comment is still empty, add just the text in italic (use underscore before and after) 109 | * if the comment is not empty, also add newlines 110 | * 111 | * @author Dario Curvino <@dudo> 112 | * 113 | * @since 2.0.3 114 | * 115 | * @param $description | the comment description build so far 116 | * @param $comment_line | the line to add into description 117 | * 118 | * @return string 119 | */ 120 | function writeCommentDescription($description, $comment_line):string { 121 | if ($description === '') { 122 | return '_' . $comment_line . '_'; 123 | } //also add newlines otherwise 124 | 125 | return "\n\n_" . $comment_line . '_'; 126 | } 127 | 128 | /** 129 | * Removes the tag from the beginning of a string and returns the remaining text. 130 | * 131 | * @param string $string 132 | * 133 | * @return string|void 134 | * @author Dario Curvino <@dudo> 135 | * @since 1.0.0 136 | * 137 | */ 138 | function removeTagFromString(string $string) { 139 | $first_word = find_first_word($string); 140 | 141 | if (is_tag($first_word)) { 142 | return trim(str_replace($first_word, '', $string)); 143 | } 144 | } 145 | 146 | /** 147 | * In a doc block the type come after the tag and before the argument. 148 | * When this method is called, the tag has been removed. 149 | * So, if there is a word, must be the type 150 | * 151 | * @param string $string the string where to search the type 152 | * 153 | * @return string 154 | * @author Dario Curvino <@dudo> 155 | * @since 1.0.0 156 | * 157 | */ 158 | function findType(string $string): string { 159 | $first_word = find_first_word($string); 160 | 161 | if (!is_tag($first_word) && !is_argument($first_word)) { 162 | return $first_word; 163 | } 164 | 165 | return ''; 166 | } 167 | 168 | /** 169 | * Find the name of the first variable inside a string 170 | * 171 | * @param $string 172 | * 173 | * @return string 174 | * @author Dario Curvino <@dudo> 175 | * @since 1.0.0 176 | * 177 | */ 178 | function findArgument($string): string { 179 | $argument = ''; 180 | 181 | $pattern = '/\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\-\>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*/'; 182 | 183 | if (preg_match($pattern, $string, $matches)) { 184 | $argument = $matches[0]; 185 | } 186 | 187 | return $argument; 188 | } 189 | 190 | /** 191 | * In a doc block the description come after the argument 192 | * So, this method remove all the text before the argument (included). 193 | * If argument is not found, do the same for the type 194 | * 195 | * @author Dario Curvino <@dudo> 196 | * 197 | * @param $string 198 | * @param $argument 199 | * @param $type 200 | * 201 | * @since 1.0.0 202 | * 203 | * @return string 204 | */ 205 | function findArgumentDescription($string, $argument, $type): string { 206 | $description = ''; 207 | $substring_to_seek = $string; 208 | 209 | if($argument) { 210 | $substring_to_seek = $argument; 211 | } 212 | else if($type) { 213 | $substring_to_seek = $type; 214 | } 215 | 216 | if ($string && $substring_to_seek) { 217 | //find the position of the argument inside the string 218 | $argument_index = strpos($string, $substring_to_seek); 219 | 220 | //get the text before the argument 221 | $text_before_desc = $argument_index + strlen($substring_to_seek); 222 | 223 | //get the description 224 | $description = substr($string, $text_before_desc); 225 | } 226 | 227 | return $description; 228 | } 229 | 230 | } 231 | 232 | } 233 | -------------------------------------------------------------------------------- /src/WPDocGen.php: -------------------------------------------------------------------------------- 1 | 6 | * @since 1.0.0 7 | */ 8 | 9 | if (!class_exists('Dudo1985\WPDocGen\WPDocGen')) { 10 | 11 | class WPDocGen { 12 | 13 | /** 14 | * String with script usage 15 | */ 16 | public const USAGE_MESSAGE = 17 | 'Usage: php wp-doc-gen.php [-e ] [-p ]'; 18 | 19 | /** 20 | * The file name (with path, eventually) to create 21 | * 22 | * @var string 23 | */ 24 | public string $file_name; 25 | 26 | /** 27 | * The folders to exclude 28 | * 29 | * @var bool|string 30 | */ 31 | public bool|string $excluded_folders = false; 32 | 33 | /** 34 | * By default, search for hooks 35 | * 36 | * @var string 37 | */ 38 | public string $function_to_seek = 'apply_filters|do_action'; 39 | 40 | /** 41 | * True if shortcodes must be searched 42 | * 43 | * @var bool 44 | */ 45 | public bool $seek_shortcode = false; 46 | /** 47 | * The prefix to look for 48 | * 49 | * @var string 50 | */ 51 | public string $prefixes = ''; 52 | 53 | /** 54 | * The number of the hooks found 55 | * 56 | * @var int 57 | */ 58 | public int $hook_count = 0; 59 | 60 | /** 61 | * The number of file processed 62 | * 63 | * @var int 64 | */ 65 | public int $files_count_php = 0; 66 | 67 | /** 68 | * by default, verbose is false 69 | * 70 | * @var bool 71 | */ 72 | public bool $verbose = false; 73 | 74 | /** 75 | * Parser instance 76 | * 77 | * @var CommentParser 78 | */ 79 | private CommentParser $parser; 80 | 81 | /** 82 | * Printer instance 83 | * 84 | * @var Printer 85 | */ 86 | private Printer $printer; 87 | 88 | /** 89 | * Init the class 90 | * 91 | * @return void 92 | * @since 1.0.0 93 | * @author Dario Curvino <@dudo> 94 | * 95 | */ 96 | public function init(): void { 97 | 98 | $this->parser = new CommentParser(); 99 | $this->printer = new Printer(); 100 | 101 | global $argv; 102 | 103 | //first, check if string has params that no needs input 104 | $this->paramsNoInput($argv); 105 | 106 | $folder_path = $argv[1]; 107 | $this->file_name = $argv[2]; 108 | 109 | //print an error and exit(1) if input folder doesn't exist 110 | $this->inputFolderExists($folder_path); 111 | 112 | //create file if it doesn't exist, print error if file can't be created or is not writable 113 | $this->checkOutputFile(); 114 | 115 | //check input params 116 | $this->checkParams($argv); 117 | 118 | $this->printer->messageWithBackground('Starting Folder exploration: ', $folder_path); 119 | 120 | $start_time = microtime(true); 121 | 122 | // Use the explore_folder function to explore the folder and write the documentation 123 | $this->exploreFolder($folder_path, true); 124 | 125 | //additional info if verbose is enabled 126 | $this->printInfo($start_time); 127 | } 128 | 129 | /** 130 | * Check if the folder to iterate exists 131 | * 132 | * @author Dario Curvino <@dudo> 133 | * @since 1.0.0 134 | * 135 | * @param $folder_path 136 | * 137 | * @return void 138 | */ 139 | function inputFolderExists($folder_path): void { 140 | if (!is_dir($folder_path)) { 141 | $this->printer->error('the input folder does not exist.'); 142 | exit(1); 143 | } 144 | } 145 | 146 | /** 147 | * Checks if the output file exists and is writable. If the file doesn't exist, 148 | * it will be created. 149 | * 150 | * @return void 151 | * @since 2.0.0 152 | * @author Dario Curvino <@dudo> 153 | */ 154 | function checkOutputFile(): void { 155 | // If the output file doesn't exist, create it 156 | if (!file_exists($this->file_name)) { 157 | $file_created = touch($this->file_name); 158 | 159 | if($file_created === false) { 160 | $this->printer->error('could not create the output file'); 161 | exit(1); 162 | } 163 | } 164 | 165 | if (!is_writable($this->file_name)) { 166 | $this->printer->error('the specified output file is not writable'); 167 | exit(1); 168 | } 169 | } 170 | 171 | /** 172 | * Check if string was called with params that doesn't need inputs: 173 | * -no params at all (will display a help message) 174 | * -h or --help 175 | * -V or --version 176 | * 177 | * @author Dario Curvino <@dudo> 178 | * 179 | * @since 2.0.3 180 | * 181 | * @param $argv 182 | * 183 | * @return void 184 | */ 185 | function paramsNoInput($argv):void { 186 | //print help message if -h or --help is used 187 | $this->helpMessage($argv); 188 | 189 | //print version if -V or --version is used 190 | $this->printVersion($argv); 191 | } 192 | 193 | /** 194 | * Manage params 195 | * 196 | * @param $argv 197 | * @return void 198 | * @author Dario Curvino <@dudo> 199 | * 200 | * @since 2.0.0 201 | */ 202 | function checkParams($argv): void{ 203 | //check if the script is called with -v or --verbose 204 | $this->verbose = $this->verboseOutput($argv); 205 | 206 | //check if --shortcode or -s was used 207 | $this->parseShortcode($argv); 208 | 209 | //check if the script is called with --exclude, or -e 210 | $this->excluded_folders = $this->getOptions($argv, '--exclude', '-e'); 211 | 212 | //check if script is called with --prefix -p param 213 | $this->prefixes = (string)$this->getOptions($argv, '--prefix', '-p'); 214 | } 215 | 216 | /** 217 | * Return help message if no params, -h or --help are used 218 | * 219 | * @author Dario Curvino <@dudo> 220 | * @since 1.0.0 221 | * 222 | * @param $argv 223 | * 224 | * @return void 225 | */ 226 | function helpMessage($argv): void { 227 | //Check if in argv exists '--help' or '-help' 228 | //also, if WPDocGen is run without params, print the help message 229 | if (!in_array('--help', $argv) && !in_array('-h', $argv) && isset($argv[1])) { 230 | return; 231 | } 232 | 233 | $this->printer->newline(); 234 | $this->printer->messageGreen('Usage'); 235 | $this->printer->message(self::USAGE_MESSAGE); 236 | $this->printer->newline(); 237 | $this->printer->messageGreen('Options'); 238 | $this->printer->helpOption('-h, --help', 'Display this help message'); 239 | $this->printer->helpOption('-V, --version','Display this application version'); 240 | $this->printer->helpOption('-s, --shortcode','Search for add_shortcode instead of hooks'); 241 | $this->printer->helpOption('-e, --exclude','Exclude the specified folders, comma separated'); 242 | $this->printer->helpOption('-p, --prefix', 'Only parse hooks starting with the specified prefix.'); 243 | $this->printer->newline(); 244 | $this->printer->messageGreen('Examples'); 245 | $this->printer->helpExamples('To scan all the files in the current directory, 246 | and save the result into the file hooks.md', 'wp-doc-gen . hooks.md'); 247 | $this->printer->newline(); 248 | $this->printer->helpExamples('To scan all the files in the current directory, 249 | excluding the dirs vendor and node_modules, and catch only hooks with prefixes \'prefix1_ prefix2_\'', 250 | 'wp-doc-gen . hooks.md --exclude vendor node_modules --prefix prefix1_ prefix2_'); 251 | 252 | exit(0); 253 | } 254 | 255 | /** 256 | * print Version if -v or --version is used 257 | * 258 | * @author Dario Curvino <@dudo> 259 | * 260 | * @since 2.0.0 261 | * 262 | * @param $argv 263 | * 264 | * @return void 265 | */ 266 | function printVersion($argv): void { 267 | if (in_array('--version', $argv) || in_array('-V', $argv)) { 268 | $this->printer->message(WPDocGenVersion); 269 | exit(0); 270 | } 271 | } 272 | 273 | /** 274 | * @param $argv 275 | * @return bool 276 | * @author Dario Curvino <@dudo> 277 | * 278 | * @since 2.0.0 279 | */ 280 | function verboseOutput($argv): bool { 281 | if (in_array('--verbose', $argv) || in_array('-v', $argv)) { 282 | return true; 283 | } 284 | return false; 285 | } 286 | 287 | /** 288 | * change $this->function_to_seek is --shortcode is enabled 289 | * 290 | * @param $argv 291 | * @return void 292 | * @author Dario Curvino <@dudo> 293 | * 294 | * @since 2.0.0 295 | */ 296 | function parseShortcode ($argv): void { 297 | if (in_array('--shortcode', $argv) || in_array('-s', $argv)) { 298 | $this->function_to_seek = 'add_shortcode'; 299 | $this->seek_shortcode = true; 300 | } 301 | } 302 | 303 | /** 304 | * Check if script is executed with passed params, and return the option 305 | * 306 | * @param $argv 307 | * @param $needle1 308 | * @param $needle2 309 | * @return false|string 310 | * @author Dario Curvino <@dudo> 311 | * @since 1.0.0 312 | * 313 | */ 314 | function getOptions($argv, $needle1, $needle2): bool|string { 315 | if (!in_array($needle1, $argv) && !in_array($needle2, $argv)) { 316 | return false; 317 | } 318 | 319 | $argv_key_index = $this->returnArrayKeyIndex($argv, $needle1, $needle2); 320 | if ($argv_key_index === false) { 321 | return false; 322 | } 323 | 324 | $option = false; 325 | 326 | if (isset($argv[$argv_key_index + 1])) { 327 | $exclude_args = array_slice($argv, $argv_key_index + 1); 328 | foreach ($exclude_args as $arg) { 329 | if (str_starts_with($arg, '-')) { 330 | break; 331 | } 332 | 333 | //add support for comma separated string 334 | //if a ',' is found at the end of the string, remove it 335 | $arg = rtrim($arg, ','); 336 | 337 | if ($option === false) { 338 | $option = $arg; 339 | } else { 340 | $option .= ', ' . $arg; 341 | } 342 | } 343 | } 344 | 345 | if ($option !== false && ($needle1 === '-e' || $needle2 === 'exclude')) { 346 | $this->printer->messageWithBackground('Excluding folders: ', $option); 347 | } 348 | 349 | return $option; 350 | } 351 | 352 | /** 353 | * Search for the first occurrence of either $key1 or $key2 in the given $argv array. 354 | * 355 | * @param array $argv An array of arguments to search through. 356 | * @param string $key1 The first key to search for in the $argv array. 357 | * @param string $key2 The second key to search for in the $argv array if $key1 is not found. 358 | * 359 | * @return int|bool Returns the index of the key if found, or false if neither key is found. 360 | * 361 | * @author Dario Curvino <@dudo> 362 | * 363 | * @since 2.0.0 364 | */ 365 | function returnArrayKeyIndex(array $argv, string $key1, string $key2): bool|int { 366 | //search the first key into $argv 367 | $key_index = array_search($key1, $argv); 368 | 369 | //if not found, try to search the second key 370 | if ($key_index === false) { 371 | $key_index = array_search($key2, $argv); 372 | } 373 | 374 | //if found and is int, return 375 | if (is_int($key_index)) { 376 | return $key_index; 377 | } 378 | 379 | return false; 380 | } 381 | 382 | /** 383 | * Explore the folder for php files 384 | * 385 | * @param $folder_path 386 | * @param bool $rewrite_file 387 | * 388 | * @return void 389 | * @since 1.0.0 390 | * 391 | * @author Dario Curvino <@dudo> 392 | */ 393 | function exploreFolder($folder_path, bool $rewrite_file = false): void { 394 | if ($rewrite_file === true) { 395 | unlink($this->file_name); 396 | } 397 | 398 | // Open the output file in write mode 399 | $file_open = fopen($this->file_name, 'a'); 400 | 401 | // Create an array of excluded folders 402 | $excluded_folders = []; 403 | if ($this->excluded_folders !== false) { 404 | $excluded_folders = explode(', ', $this->excluded_folders); 405 | } 406 | 407 | // Iterate through all the files in the folder 408 | $files = scandir($folder_path); 409 | 410 | $this->loopFolder($files, $excluded_folders, $folder_path, $file_open); 411 | 412 | // Close the output file 413 | fclose($file_open); 414 | } 415 | 416 | /** 417 | * Loop the folder 418 | * 419 | * @param $files 420 | * @param $excluded_folders 421 | * @param $folder_path 422 | * @param $file_open 423 | * @return void 424 | * @since 2.0.0 425 | * @author Dario Curvino <@dudo> 426 | * 427 | */ 428 | function loopFolder($files, $excluded_folders, $folder_path, $file_open): void { 429 | foreach ($files as $file) { 430 | //Ignore hidden folders or files 431 | if (str_starts_with($file, '.')) { 432 | continue; 433 | } 434 | 435 | //ignore this dir (.) previous dir (..) and the user defined folder to exclude 436 | if ($file === '.' || $file === '..' || in_array($file, $excluded_folders)) { 437 | continue; 438 | } 439 | 440 | $file_path = $folder_path . '/' . $file; 441 | 442 | // If the file is a folder, call again exploreFolder 443 | if (is_dir($file_path)) { 444 | if($this->verbose === true) { 445 | $this->printer->messageWithBackground('Exploring folder ', $file_path); 446 | } 447 | $this->exploreFolder($file_path); 448 | } 449 | else { 450 | //here means that is a php file, so analyze it and eventually write the doc 451 | $this->analyzePhpFile($file_path, $file_open); 452 | } 453 | } 454 | } 455 | 456 | /** 457 | * Look for apply_filter and do_action in a file 458 | * If prefix is not provided, return them all 459 | * 460 | * @author Dario Curvino <@dudo> 461 | * @since 1.0.0 462 | * 463 | * @param $file_path 464 | * @param $file_open 465 | * 466 | * @return void 467 | */ 468 | function analyzePhpFile($file_path, $file_open): void { 469 | $extension = pathinfo($file_path, PATHINFO_EXTENSION); 470 | if ($extension !== 'php') { 471 | return; 472 | } 473 | 474 | // Open the file in read mode 475 | $file_content = file_get_contents($file_path); 476 | 477 | $prefixes = explode(', ', $this->prefixes); 478 | 479 | foreach ($prefixes as $prefix) { 480 | // Find occurrences of the apply_filters and do_action functions 481 | $matches = []; 482 | $num_matches = preg_match_all( 483 | '/\b(' .$this->function_to_seek. ')\b\s*\(\s*[\'"](' . $prefix . '[^\'"]+)[\'"]/', $file_content, 484 | $matches, PREG_OFFSET_CAPTURE 485 | ); 486 | if ($num_matches > 0) { 487 | $this->writeFile($file_open, $file_path, $matches, $file_content); 488 | } 489 | } 490 | 491 | $this->files_count_php++; 492 | } 493 | 494 | /** 495 | * Write the file 496 | * 497 | * @author Dario Curvino <@dudo> 498 | * @since 1.0.0 499 | * 500 | * @param $file_open 501 | * @param $file_path 502 | * @param $matches 503 | * @param $file_content 504 | * 505 | * @return void 506 | */ 507 | function writeFile($file_open, $file_path, $matches, $file_content): void { 508 | // Write information about the functions to the output file 509 | foreach ($matches[0] as $index => $match) { 510 | $hook_type = $matches[1][$index][0]; 511 | $hook_name = $matches[2][$index][0]; 512 | $match_offset = $matches[0][$index][1]; 513 | //get the line number 514 | $line_number = substr_count(substr($file_content, 0, $match_offset), "\n") + 1; 515 | $link = "Source: [$file_path, line $line_number]($file_path:$line_number)"; 516 | 517 | //get the comment 518 | $comment = $this->parser->getDocCommentByLine($file_path, $line_number); 519 | 520 | //write the hook and the link to file and line 521 | //e.g. do_action('yasr_add_admin_scripts_begin') 522 | //Source: ../yet-another-stars-rating/admin/classes/YasrAdmin.php, line 155 523 | if($this->seek_shortcode === true) { 524 | fwrite($file_open, "\n ### Shortcode $hook_name \n\n $link\n"); 525 | } else { 526 | fwrite($file_open, "\n ### `$hook_type('$hook_name')` \n\n $link\n"); 527 | } 528 | 529 | //write the comment if exists 530 | $this->writeComment($comment, $file_open); 531 | 532 | fwrite($file_open, '___'); 533 | 534 | $this->hook_count++; 535 | } 536 | fwrite($file_open, "\n"); 537 | } 538 | 539 | /** 540 | * @param $comment 541 | * @param $file_open 542 | * 543 | * @return void 544 | * @since 1.0.0 545 | * 546 | * @author Dario Curvino <@dudo> 547 | */ 548 | function writeComment($comment, $file_open): void { 549 | if (!empty($comment)) { 550 | if (isset($comment['description']) && $comment['description'] !== '') { 551 | $description = $comment['description']; 552 | fwrite($file_open, "\n"); 553 | fwrite($file_open, $description . "\n\n"); 554 | } 555 | 556 | //do not write table of args for add_shortcode 557 | if($this->seek_shortcode !== true) { 558 | if (isset($comment['args']) && $comment['args'] !== '') { 559 | $args = $comment['args']; 560 | $this->writeTable($file_open, $args); 561 | } 562 | } 563 | } 564 | } 565 | 566 | /** 567 | * @param $file_open 568 | * @param $args array Here $args must begin with an argument, then type and description 569 | * 570 | * @return void 571 | * @since 1.0.0 572 | * 573 | * @author Dario Curvino <@dudo> 574 | */ 575 | function writeTable($file_open, array $args): void { 576 | $t = new MarkdownTable(); 577 | $headers = ['Argument', 'Type', 'Description']; 578 | 579 | foreach ($args as $arg) { 580 | //remove multiple consecutive whitespaces 581 | $arg = WPDocGen::removeMultipleWhitespaces($arg); 582 | 583 | $argument_type = $this->parser->findType($arg); 584 | $argument_name = $this->parser->findArgument($arg); 585 | $argument_desc = $this->parser->findArgumentDescription($arg, $argument_name, $argument_type); 586 | 587 | $t->addRow([$argument_name, $argument_type, $argument_desc]); 588 | } 589 | 590 | $t->addHeader($headers); 591 | 592 | fwrite($file_open, $t->getTable()); 593 | } 594 | 595 | /** 596 | * Print additional info if verbose is enabled 597 | * 598 | * @param $start_time 599 | * @return void 600 | * @author Dario Curvino <@dudo> 601 | * 602 | * @since 603 | */ 604 | public function printInfo($start_time): void { 605 | $this->printer->message('Finished folder exploration'. "\n"); 606 | 607 | if($this->verbose === true) { 608 | $processed_php_file_text = ANSI_GREEN. $this->files_count_php .ANSI_RESET. ' php files have been processed'; 609 | 610 | $end_time = microtime(true); 611 | $total_time = $end_time - $start_time; 612 | 613 | $this->printer->message($processed_php_file_text); 614 | $this->printer->message('Execution time: ' . $total_time . ' seconds'); 615 | } 616 | 617 | if($this->seek_shortcode === true) { 618 | $type = ' shortcodes'; 619 | } else { 620 | $type = ' hooks'; 621 | } 622 | 623 | $this->printer->message(ANSI_GREEN. $this->hook_count .ANSI_RESET. $type . ' has been found'); 624 | $this->printer->message('File ' . $this->printer->returnStringWithBackground($this->file_name) . 625 | ' saved successfully.'); 626 | } 627 | 628 | /** 629 | * Remove multiple whitespaces from a string 630 | * 631 | * @param $string 632 | * 633 | * @return string 634 | * @author Dario Curvino <@dudo> 635 | * @since 1.0.0 636 | * 637 | */ 638 | static function removeMultipleWhitespaces($string): string{ 639 | return preg_replace('/\s+/', ' ', $string); 640 | } 641 | 642 | } 643 | 644 | } 645 | --------------------------------------------------------------------------------