From 12c5b34fe16f1e9590cce754c8b8fd47a8861292 Mon Sep 17 00:00:00 2001 From: Thoriq Firdaus <2067467+tfirdaus@users.noreply.github.com> Date: Tue, 9 Apr 2024 05:41:02 +0800 Subject: [PATCH 1/9] Add `test_up_to` field --- features/plugin.feature | 62 +++++++++- src/Plugin_Command.php | 10 ++ src/WP_CLI/ParsePluginReadme.php | 199 +++++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 src/WP_CLI/ParsePluginReadme.php diff --git a/features/plugin.feature b/features/plugin.feature index d853a0f5..8e3e2c5d 100644 --- a/features/plugin.feature +++ b/features/plugin.feature @@ -649,7 +649,7 @@ Feature: Manage WordPress plugins When I run `wp plugin list --name=hello-dolly --field=version` And save STDOUT as {PLUGIN_VERSION} - + When I run `wp plugin list --name=hello-dolly --field=update_version` And save STDOUT as {UPDATE_VERSION} @@ -720,3 +720,63 @@ Feature: Manage WordPress plugins Then STDOUT should be a table containing rows: | name | auto_update | | hello | on | + + Scenario: Listing plugins should include tested_up_to from the 'tested up to' header + Given a WP install + And a wp-content/plugins/foo/foo.php file: + """ + $mu_description, 'file' => $file, 'auto_update' => false, + 'tested_up_to' => '', 'wporg_status' => $wporg_info['status'], 'wporg_last_updated' => $wporg_info['last_updated'], ); @@ -286,6 +290,7 @@ protected function get_all_items() { 'file' => $name, 'auto_update' => false, 'author' => $item_data['Author'], + 'tested_up_to' => '', 'wporg_status' => '', 'wporg_last_updated' => '', ]; @@ -739,6 +744,10 @@ protected function get_item_list() { 'wporg_last_updated' => $wporg_info['last_updated'], ]; + // Include information from the plugin readme.txt headers. + $plugin_headers = $this->get_plugin_headers( $name ); + $items[ $file ]['tested_up_to'] = isset( $plugin_headers['tested_up_to'] ) ? $plugin_headers['tested_up_to'] : ''; + if ( null === $update_info ) { // Get info for all plugins that don't have an update. $plugin_update_info = isset( $all_update_info->no_update[ $file ] ) ? $all_update_info->no_update[ $file ] : null; @@ -1251,6 +1260,7 @@ public function delete( $args, $assoc_args = array() ) { * * version * * update_version * * auto_update + * * tested_up_to * * These fields are optionally available: * diff --git a/src/WP_CLI/ParsePluginReadme.php b/src/WP_CLI/ParsePluginReadme.php new file mode 100644 index 00000000..631e9ea3 --- /dev/null +++ b/src/WP_CLI/ParsePluginReadme.php @@ -0,0 +1,199 @@ + 'tested_up_to', + 'tested up to' => 'tested_up_to', + ); + + /** + * Parse readme from the plugin name. + * + * @param string $name The plugin name e.g. "classic-editor". + */ + private function parse_readme( $name = '' ) { + $plugin_readme = WP_PLUGIN_DIR . '/' . $name . '/readme.txt'; + + if ( ! file_exists( $plugin_readme ) ) { + // Reset the plugin headers if the readme.txt file does not exists + // It ensures that it does not carry out stale data from a previous parsed plugin. + $this->plugin_headers = []; + + return; + } + + $context = stream_context_create( + array( + 'http' => array( + 'user_agent' => 'WordPress.org Plugin Readme Parser', + ), + ) + ); + $contents = file_get_contents( $plugin_readme, false, $context ); + + // At the moment, the parser only concern about parsing the plugin headers, which + // appear after the plugin name header, and before the description header. + if ( preg_match( '/=== .*? ===\s*(.*?)(?:== Description ==|$)/s', $contents, $matches ) ) { + $contents = trim( $matches[1] ); + } + + if ( preg_match( '!!u', $contents ) ) { + $contents = preg_split( '!\R!u', $contents ); + } else { + $contents = preg_split( '!\R!', $contents ); // regex failed due to invalid UTF8 in $contents, see #2298 + } + $contents = array_map( array( $this, 'strip_newlines' ), $contents ); + + // Strip UTF8 BOM if present. + if ( 0 === strpos( $contents[0], "\xEF\xBB\xBF" ) ) { + $contents[0] = substr( $contents[0], 3 ); + } + + // Convert UTF-16 files. + if ( 0 === strpos( $contents[0], "\xFF\xFE" ) ) { + foreach ( $contents as $i => $line ) { + $contents[ $i ] = mb_convert_encoding( $line, 'UTF-8', 'UTF-16' ); + } + } + + $line = $this->get_first_nonwhitespace( $contents ); + $last_line_was_blank = false; + + do { + $value = null; + $header = $this->parse_possible_header( $line ); + $line = array_shift( $contents ); + + // If it doesn't look like a header value, maybe break to the next section. + if ( ! $header ) { + if ( empty( $line ) ) { + // Some plugins have line-breaks within the headers... + $last_line_was_blank = true; + continue; + } else { + // We've hit a line that is not blank, but also doesn't look like a header, assume the Short Description and end Header parsing. + break; + } + } + + list( $key, $value ) = $header; + + if ( isset( $this->valid_plugin_headers[ $key ] ) ) { + $header_key = $this->valid_plugin_headers[ $key ]; + + if ( 'tested_up_to' === $header_key && $value ) { + $this->plugin_headers['tested_up_to'] = $this->sanitize_tested_version( $value ); + } + + $this->plugin_headers[ $this->valid_plugin_headers[ $key ] ] = $value; + } elseif ( $last_line_was_blank ) { + // If we skipped over a blank line, and then ended up with an unexpected header, assume we parsed too far and ended up in the Short Description. + // This final line will be added back into the stack after the loop for further parsing. + break; + } + $last_line_was_blank = false; + } while ( null !== $line ); + } + + /** + * Gets the plugin header information from the plugin's readme.txt file. + * + * @param string $name The plugin name e.g. "classic-editor". + * @return array + */ + protected function get_plugin_headers( $name ) { + $this->parse_readme( $name ); + + return $this->plugin_headers; + } + + /** + * Parse a line to see if it's a header. + * + * @param string $line The line from the readme to parse. + * @param bool $only_valid Whether to only return a valid known header. + * @return false|array + */ + private function parse_possible_header( $line, $only_valid = false ) { + if ( ! str_contains( $line, ':' ) || str_starts_with( $line, '#' ) || str_starts_with( $line, '=' ) ) { + return false; + } + + list( $key, $value ) = explode( ':', $line, 2 ); + $key = strtolower( trim( $key, " \t*-\r\n" ) ); + $value = trim( $value, " \t*-\r\n" ); + + if ( $only_valid && ! isset( $this->valid_headers[ $key ] ) ) { + return false; + } + + return array( $key, $value ); + } + + /** + * Sanitizes the Tested header to ensure that it's a valid version header. + * + * @param string $version + * @return string The sanitized $version + */ + private function sanitize_tested_version( $version ) { + $version = trim( $version ); + + if ( $version ) { + + // Handle the edge-case of 'WordPress 5.0' and 'WP 5.0' for historical purposes. + $strip_phrases = [ + 'WordPress', + 'WP', + ]; + $version = trim( str_ireplace( $strip_phrases, '', $version ) ); + + // Strip off any -alpha, -RC, -beta suffixes, as these complicate comparisons and are rarely used. + list( $version, ) = explode( '-', $version ); + } + + return $version; + } + + /** + * @param string $line + * @return string + */ + private function strip_newlines( $line ) { + return rtrim( $line, "\r\n" ); + } + + /** + * @param array $contents + * @return string + */ + private function get_first_nonwhitespace( &$contents ) { + $line = array_shift( $contents ); + + while ( null !== $line ) { + $trimmed = trim( $line ); + + if ( ! empty( $trimmed ) ) { + break; + } + + $line = array_shift( $contents ); + } + + return $line ? $line : ''; + } +} From 0e9f37a410368170b2f1ecab2967602bc75e3aab Mon Sep 17 00:00:00 2001 From: Thoriq Firdaus <2067467+tfirdaus@users.noreply.github.com> Date: Tue, 9 Apr 2024 06:06:09 +0800 Subject: [PATCH 2/9] Update `obj_fields` --- src/Plugin_Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugin_Command.php b/src/Plugin_Command.php index 66428b54..69a6a220 100644 --- a/src/Plugin_Command.php +++ b/src/Plugin_Command.php @@ -61,7 +61,7 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade { 'version', 'update_version', 'auto_update', - 'tested', + 'tested_up_to', ); /** From 8cd061409c4b40912dc31f405bb101ac0d524e82 Mon Sep 17 00:00:00 2001 From: Thoriq Firdaus <2067467+tfirdaus@users.noreply.github.com> Date: Tue, 9 Apr 2024 06:07:05 +0800 Subject: [PATCH 3/9] Update obj_fields --- src/Plugin_Command.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Plugin_Command.php b/src/Plugin_Command.php index 69a6a220..4cf9062c 100644 --- a/src/Plugin_Command.php +++ b/src/Plugin_Command.php @@ -61,7 +61,6 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade { 'version', 'update_version', 'auto_update', - 'tested_up_to', ); /** From 9d2d2012828bbd528384d6a655de5b4f232edf35 Mon Sep 17 00:00:00 2001 From: Thoriq Firdaus <2067467+tfirdaus@users.noreply.github.com> Date: Tue, 9 Apr 2024 06:08:19 +0800 Subject: [PATCH 4/9] Rename WordPress.org to WP-CLI --- src/WP_CLI/ParsePluginReadme.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WP_CLI/ParsePluginReadme.php b/src/WP_CLI/ParsePluginReadme.php index 631e9ea3..70f31ee6 100644 --- a/src/WP_CLI/ParsePluginReadme.php +++ b/src/WP_CLI/ParsePluginReadme.php @@ -39,7 +39,7 @@ private function parse_readme( $name = '' ) { $context = stream_context_create( array( 'http' => array( - 'user_agent' => 'WordPress.org Plugin Readme Parser', + 'user_agent' => 'WP-CLI Plugin Readme Parser', ), ) ); From 73b482ce1c8238865b1fe64a8e3d7175e4d8a70c Mon Sep 17 00:00:00 2001 From: Thoriq Firdaus <2067467+tfirdaus@users.noreply.github.com> Date: Tue, 9 Apr 2024 06:09:30 +0800 Subject: [PATCH 5/9] Fix alignment spacing --- src/Plugin_Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugin_Command.php b/src/Plugin_Command.php index 4cf9062c..79e32d72 100644 --- a/src/Plugin_Command.php +++ b/src/Plugin_Command.php @@ -744,7 +744,7 @@ protected function get_item_list() { ]; // Include information from the plugin readme.txt headers. - $plugin_headers = $this->get_plugin_headers( $name ); + $plugin_headers = $this->get_plugin_headers( $name ); $items[ $file ]['tested_up_to'] = isset( $plugin_headers['tested_up_to'] ) ? $plugin_headers['tested_up_to'] : ''; if ( null === $update_info ) { From b207e081b3f727f4e55f28f2a0baced4b6456865 Mon Sep 17 00:00:00 2001 From: Thoriq Firdaus <2067467+tfirdaus@users.noreply.github.com> Date: Tue, 9 Apr 2024 19:07:46 +0800 Subject: [PATCH 6/9] Use `afragen/wordpress-plugin-readme-parser` package --- composer.json | 1 + src/Plugin_Command.php | 12 +- src/WP_CLI/ParsePluginReadme.php | 199 ------------------------------- 3 files changed, 9 insertions(+), 203 deletions(-) delete mode 100644 src/WP_CLI/ParsePluginReadme.php diff --git a/composer.json b/composer.json index d68f3493..cbb1c8f5 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ } ], "require": { + "afragen/wordpress-plugin-readme-parser": "dev-master", "composer/semver": "^1.4 || ^2 || ^3", "wp-cli/wp-cli": "^2.10" }, diff --git a/src/Plugin_Command.php b/src/Plugin_Command.php index 79e32d72..25d1f113 100644 --- a/src/Plugin_Command.php +++ b/src/Plugin_Command.php @@ -1,7 +1,7 @@ $file, 'auto_update' => in_array( $file, $auto_updates, true ), 'author' => $details['Author'], + 'tested_up_to' => '', 'wporg_status' => $wporg_info['status'], 'wporg_last_updated' => $wporg_info['last_updated'], ]; // Include information from the plugin readme.txt headers. - $plugin_headers = $this->get_plugin_headers( $name ); - $items[ $file ]['tested_up_to'] = isset( $plugin_headers['tested_up_to'] ) ? $plugin_headers['tested_up_to'] : ''; + $plugin_readme = WP_PLUGIN_DIR . '/' . $name . '/readme.txt'; + + if ( file_exists( $plugin_readme ) ) { + $readme_parser = new Parser( $plugin_readme ); + $items[ $file ]['tested_up_to'] = $readme_parser->tested ? $readme_parser->tested : ''; + } if ( null === $update_info ) { // Get info for all plugins that don't have an update. diff --git a/src/WP_CLI/ParsePluginReadme.php b/src/WP_CLI/ParsePluginReadme.php deleted file mode 100644 index 70f31ee6..00000000 --- a/src/WP_CLI/ParsePluginReadme.php +++ /dev/null @@ -1,199 +0,0 @@ - 'tested_up_to', - 'tested up to' => 'tested_up_to', - ); - - /** - * Parse readme from the plugin name. - * - * @param string $name The plugin name e.g. "classic-editor". - */ - private function parse_readme( $name = '' ) { - $plugin_readme = WP_PLUGIN_DIR . '/' . $name . '/readme.txt'; - - if ( ! file_exists( $plugin_readme ) ) { - // Reset the plugin headers if the readme.txt file does not exists - // It ensures that it does not carry out stale data from a previous parsed plugin. - $this->plugin_headers = []; - - return; - } - - $context = stream_context_create( - array( - 'http' => array( - 'user_agent' => 'WP-CLI Plugin Readme Parser', - ), - ) - ); - $contents = file_get_contents( $plugin_readme, false, $context ); - - // At the moment, the parser only concern about parsing the plugin headers, which - // appear after the plugin name header, and before the description header. - if ( preg_match( '/=== .*? ===\s*(.*?)(?:== Description ==|$)/s', $contents, $matches ) ) { - $contents = trim( $matches[1] ); - } - - if ( preg_match( '!!u', $contents ) ) { - $contents = preg_split( '!\R!u', $contents ); - } else { - $contents = preg_split( '!\R!', $contents ); // regex failed due to invalid UTF8 in $contents, see #2298 - } - $contents = array_map( array( $this, 'strip_newlines' ), $contents ); - - // Strip UTF8 BOM if present. - if ( 0 === strpos( $contents[0], "\xEF\xBB\xBF" ) ) { - $contents[0] = substr( $contents[0], 3 ); - } - - // Convert UTF-16 files. - if ( 0 === strpos( $contents[0], "\xFF\xFE" ) ) { - foreach ( $contents as $i => $line ) { - $contents[ $i ] = mb_convert_encoding( $line, 'UTF-8', 'UTF-16' ); - } - } - - $line = $this->get_first_nonwhitespace( $contents ); - $last_line_was_blank = false; - - do { - $value = null; - $header = $this->parse_possible_header( $line ); - $line = array_shift( $contents ); - - // If it doesn't look like a header value, maybe break to the next section. - if ( ! $header ) { - if ( empty( $line ) ) { - // Some plugins have line-breaks within the headers... - $last_line_was_blank = true; - continue; - } else { - // We've hit a line that is not blank, but also doesn't look like a header, assume the Short Description and end Header parsing. - break; - } - } - - list( $key, $value ) = $header; - - if ( isset( $this->valid_plugin_headers[ $key ] ) ) { - $header_key = $this->valid_plugin_headers[ $key ]; - - if ( 'tested_up_to' === $header_key && $value ) { - $this->plugin_headers['tested_up_to'] = $this->sanitize_tested_version( $value ); - } - - $this->plugin_headers[ $this->valid_plugin_headers[ $key ] ] = $value; - } elseif ( $last_line_was_blank ) { - // If we skipped over a blank line, and then ended up with an unexpected header, assume we parsed too far and ended up in the Short Description. - // This final line will be added back into the stack after the loop for further parsing. - break; - } - $last_line_was_blank = false; - } while ( null !== $line ); - } - - /** - * Gets the plugin header information from the plugin's readme.txt file. - * - * @param string $name The plugin name e.g. "classic-editor". - * @return array - */ - protected function get_plugin_headers( $name ) { - $this->parse_readme( $name ); - - return $this->plugin_headers; - } - - /** - * Parse a line to see if it's a header. - * - * @param string $line The line from the readme to parse. - * @param bool $only_valid Whether to only return a valid known header. - * @return false|array - */ - private function parse_possible_header( $line, $only_valid = false ) { - if ( ! str_contains( $line, ':' ) || str_starts_with( $line, '#' ) || str_starts_with( $line, '=' ) ) { - return false; - } - - list( $key, $value ) = explode( ':', $line, 2 ); - $key = strtolower( trim( $key, " \t*-\r\n" ) ); - $value = trim( $value, " \t*-\r\n" ); - - if ( $only_valid && ! isset( $this->valid_headers[ $key ] ) ) { - return false; - } - - return array( $key, $value ); - } - - /** - * Sanitizes the Tested header to ensure that it's a valid version header. - * - * @param string $version - * @return string The sanitized $version - */ - private function sanitize_tested_version( $version ) { - $version = trim( $version ); - - if ( $version ) { - - // Handle the edge-case of 'WordPress 5.0' and 'WP 5.0' for historical purposes. - $strip_phrases = [ - 'WordPress', - 'WP', - ]; - $version = trim( str_ireplace( $strip_phrases, '', $version ) ); - - // Strip off any -alpha, -RC, -beta suffixes, as these complicate comparisons and are rarely used. - list( $version, ) = explode( '-', $version ); - } - - return $version; - } - - /** - * @param string $line - * @return string - */ - private function strip_newlines( $line ) { - return rtrim( $line, "\r\n" ); - } - - /** - * @param array $contents - * @return string - */ - private function get_first_nonwhitespace( &$contents ) { - $line = array_shift( $contents ); - - while ( null !== $line ) { - $trimmed = trim( $line ); - - if ( ! empty( $trimmed ) ) { - break; - } - - $line = array_shift( $contents ); - } - - return $line ? $line : ''; - } -} From b7d6b58ae2d667142445962a94f9f7aaeb4f5b32 Mon Sep 17 00:00:00 2001 From: Thoriq Firdaus <2067467+tfirdaus@users.noreply.github.com> Date: Tue, 9 Apr 2024 20:05:24 +0800 Subject: [PATCH 7/9] Mark `tested_up_to` as optional --- features/plugin.feature | 24 ++++++++++++++++++++++++ src/Plugin_Command.php | 21 +++++++++++++++------ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/features/plugin.feature b/features/plugin.feature index 8e3e2c5d..73f910dd 100644 --- a/features/plugin.feature +++ b/features/plugin.feature @@ -746,11 +746,23 @@ Feature: Manage WordPress plugins License URI: https://www.gnu.org/licenses/gpl-2.0.html """ And I run `wp plugin activate foo` + + When I run `wp plugin list` + Then STDOUT should be a table containing rows: + | name | status | update | version | update_version | auto_update | + | foo | active | none | | | off | + When I run `wp plugin list --fields=name,tested_up_to` Then STDOUT should be a table containing rows: | name | tested_up_to | | foo | 3.4 | + And I run `wp plugin list --name=foo --field=tested_up_to` + Then STDOUT should be: + """ + 3.4 + """ + Scenario: Listing plugins should include tested_up_to from the 'tested' header Given a WP install And a wp-content/plugins/foo/foo.php file: @@ -776,7 +788,19 @@ Feature: Manage WordPress plugins License URI: https://www.gnu.org/licenses/gpl-2.0.html """ And I run `wp plugin activate foo` + + When I run `wp plugin list` + Then STDOUT should be a table containing rows: + | name | status | update | version | update_version | auto_update | + | foo | active | none | | | off | + When I run `wp plugin list --fields=name,tested_up_to` Then STDOUT should be a table containing rows: | name | tested_up_to | | foo | 5.5 | + + And I run `wp plugin list --name=foo --field=tested_up_to` + Then STDOUT should be: + """ + 5.5 + """ diff --git a/src/Plugin_Command.php b/src/Plugin_Command.php index 25d1f113..91aebb5e 100644 --- a/src/Plugin_Command.php +++ b/src/Plugin_Command.php @@ -52,6 +52,9 @@ class Plugin_Command extends \WP_CLI\CommandWithUpgrade { 'status' => false, 'last_updated' => false, ]; + protected $check_headers = [ + 'tested_up_to' => false, + ]; protected $obj_fields = array( 'name', @@ -743,12 +746,14 @@ protected function get_item_list() { 'wporg_last_updated' => $wporg_info['last_updated'], ]; - // Include information from the plugin readme.txt headers. - $plugin_readme = WP_PLUGIN_DIR . '/' . $name . '/readme.txt'; + if ( $this->check_headers['tested_up_to'] ) { + // Include information from the plugin readme.txt headers. + $plugin_readme = WP_PLUGIN_DIR . '/' . $name . '/readme.txt'; - if ( file_exists( $plugin_readme ) ) { - $readme_parser = new Parser( $plugin_readme ); - $items[ $file ]['tested_up_to'] = $readme_parser->tested ? $readme_parser->tested : ''; + if ( file_exists( $plugin_readme ) ) { + $readme_parser = new Parser( $plugin_readme ); + $items[ $file ]['tested_up_to'] = $readme_parser->tested ? $readme_parser->tested : ''; + } } if ( null === $update_info ) { @@ -1263,7 +1268,6 @@ public function delete( $args, $assoc_args = array() ) { * * version * * update_version * * auto_update - * * tested_up_to * * These fields are optionally available: * @@ -1273,6 +1277,7 @@ public function delete( $args, $assoc_args = array() ) { * * description * * file * * author + * * tested_up_to * * wporg_status * * wporg_last_updated * @@ -1316,6 +1321,8 @@ public function list_( $_, $assoc_args ) { $fields = explode( ',', $fields ); $this->check_wporg['status'] = in_array( 'wporg_status', $fields, true ); $this->check_wporg['last_updated'] = in_array( 'wporg_last_updated', $fields, true ); + + $this->check_headers['tested_up_to'] = in_array( 'tested_up_to', $fields, true ); } $field = Utils\get_flag_value( $assoc_args, 'field' ); @@ -1325,6 +1332,8 @@ public function list_( $_, $assoc_args ) { $this->check_wporg['last_updated'] = true; } + $this->check_headers['tested_up_to'] = 'tested_up_to' === $field || $this->check_headers['tested_up_to']; + parent::_list( $_, $assoc_args ); } From 4a1c2c7ec97716e6979549fe8f5f6053d446c69e Mon Sep 17 00:00:00 2001 From: Thoriq Firdaus <2067467+tfirdaus@users.noreply.github.com> Date: Fri, 26 Apr 2024 23:10:51 +0700 Subject: [PATCH 8/9] Refactor readme parser --- composer.json | 1 - src/Plugin_Command.php | 31 +++++++++++++++++++++++++------ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index cbb1c8f5..d68f3493 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,6 @@ } ], "require": { - "afragen/wordpress-plugin-readme-parser": "dev-master", "composer/semver": "^1.4 || ^2 || ^3", "wp-cli/wp-cli": "^2.10" }, diff --git a/src/Plugin_Command.php b/src/Plugin_Command.php index 91aebb5e..489ea62b 100644 --- a/src/Plugin_Command.php +++ b/src/Plugin_Command.php @@ -1,10 +1,11 @@ check_headers['tested_up_to'] ) { - // Include information from the plugin readme.txt headers. - $plugin_readme = WP_PLUGIN_DIR . '/' . $name . '/readme.txt'; + $plugin_readme = normalize_path( WP_PLUGIN_DIR . '/' . $name . '/readme.txt' ); + + if ( file_exists( $plugin_readme ) && is_readable( $plugin_readme ) ) { + $readme_obj = new SplFileObject( $plugin_readme ); + $readme_obj->setFlags( SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY ); + $readme_line = 0; + + // Reading the whole file can exhaust the memory, so only read the first 100 lines of the file, + // as the "Tested up to" header should be near the top. + while ( $readme_line < 100 && ! $readme_obj->eof() ) { + $line = $readme_obj->fgets(); + + // Similar to WP.org, it matches for both "Tested up to" and "Tested" header in the readme file. + preg_match( '/^tested(:| up to:) (.*)$/i', strtolower( $line ), $matches ); + + if ( isset( $matches[2] ) && ! empty( $matches[2] ) ) { + $items[ $file ]['tested_up_to'] = $matches[2]; + break; + } + + ++$readme_line; + } - if ( file_exists( $plugin_readme ) ) { - $readme_parser = new Parser( $plugin_readme ); - $items[ $file ]['tested_up_to'] = $readme_parser->tested ? $readme_parser->tested : ''; + $file_obj = null; } } From c94e23f88d2e3f4ce61c310c6dc151f1d2a0de43 Mon Sep 17 00:00:00 2001 From: Daniel Bachhuber Date: Fri, 26 Apr 2024 13:50:37 -0700 Subject: [PATCH 9/9] Slightly different approach to generating the path --- src/Plugin_Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugin_Command.php b/src/Plugin_Command.php index 489ea62b..6012b927 100644 --- a/src/Plugin_Command.php +++ b/src/Plugin_Command.php @@ -748,7 +748,7 @@ protected function get_item_list() { ]; if ( $this->check_headers['tested_up_to'] ) { - $plugin_readme = normalize_path( WP_PLUGIN_DIR . '/' . $name . '/readme.txt' ); + $plugin_readme = normalize_path( dirname( WP_PLUGIN_DIR . '/' . $file ) . '/readme.txt' ); if ( file_exists( $plugin_readme ) && is_readable( $plugin_readme ) ) { $readme_obj = new SplFileObject( $plugin_readme );