From 17828872b1e511e2ace963e9f5880ecf1bf8872d Mon Sep 17 00:00:00 2001 From: Christoph Daum Date: Mon, 13 Apr 2026 19:18:56 +0200 Subject: [PATCH] fix(report): add local readme.txt fallback for "Tested up to" Parse the "Tested up to" header from the plugin's local readme.txt when the wordpress.org API does not provide a value. This covers non-wordpress.org plugins (custom Update URI) and cases where the API returns no tested value. Closes #64 --- rt-plugin-report.php | 45 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/rt-plugin-report.php b/rt-plugin-report.php index 6b76f78..588b78c 100644 --- a/rt-plugin-report.php +++ b/rt-plugin-report.php @@ -322,6 +322,9 @@ private function assemble_plugin_report( $slug ) { // Change any whitespace to default space. $report['local_info']['Name'] = preg_replace( '/\s+/u', ' ', $report['local_info']['Name'] ); + // Parse the local readme.txt for the "Tested up to" value. + $report['local_tested'] = $this->get_local_tested_up_to( $key ); + break; } } @@ -406,6 +409,45 @@ private function check_exists_in_svn( $slug ) { } + /** + * Parse the "Tested up to" value from a plugin's local readme.txt file. + * + * @param string $plugin_file Relative plugin file path (e.g. "plugin-name/plugin-name.php"). + * + * @return string|null The "Tested up to" version string, or null if not found. + */ + private function get_local_tested_up_to( $plugin_file ) { + $plugin_dir = WP_PLUGIN_DIR . '/' . dirname( $plugin_file ); + + // Try readme.txt first, then readme.md. + $readme_path = null; + foreach ( array( 'readme.txt', 'README.txt', 'readme.md', 'README.md' ) as $filename ) { + if ( file_exists( $plugin_dir . '/' . $filename ) ) { + $readme_path = $plugin_dir . '/' . $filename; + break; + } + } + + if ( ! $readme_path ) { + return null; + } + + // Read only the first 8 KB — headers are always at the top. + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + $content = file_get_contents( $readme_path, false, null, 0, 8192 ); + if ( false === $content ) { + return null; + } + + // Match "Tested up to: " (case-insensitive). + if ( preg_match( '/^[ \t]*tested up to[ \t]*:[ \t]*(\d[^\r\n]*)/mi', $content, $matches ) ) { + return trim( $matches[1] ); + } + + return null; + } + + /** * From a report, generate an HTML table row with relevant data for the plugin. * @@ -546,6 +588,9 @@ private function render_table_row( $report ) { if ( isset( $report['repo_info'] ) && isset( $report['repo_info']->tested ) && ! empty( $report['repo_info']->tested ) ) { $css_class = $this->get_version_risk_classname( $report['repo_info']->tested, $wp_latest, true ); $html .= '' . esc_html( $report['repo_info']->tested ) . ''; + } elseif ( isset( $report['local_tested'] ) && ! empty( $report['local_tested'] ) ) { + $css_class = $this->get_version_risk_classname( $report['local_tested'], $wp_latest, true ); + $html .= '' . esc_html( $report['local_tested'] ) . ''; } else { $html .= $this->render_error_cell(); }