├── .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 |
--------------------------------------------------------------------------------