diff --git a/wordpress.org/public_html/wp-content/plugins/plugin-directory/cli/class-import.php b/wordpress.org/public_html/wp-content/plugins/plugin-directory/cli/class-import.php index 1abbec62b1..70234d289f 100644 --- a/wordpress.org/public_html/wp-content/plugins/plugin-directory/cli/class-import.php +++ b/wordpress.org/public_html/wp-content/plugins/plugin-directory/cli/class-import.php @@ -196,6 +196,8 @@ public function import_from_svn( $plugin_slug, $svn_changed_tags = array( 'trunk ]; } + $this->warnings = array_merge( $this->warnings, self::requires_header_warnings( $headers, $readme ) ); + /* * Check to see if the plugin is using the `Update URI` header. * @@ -1496,6 +1498,55 @@ public static function version_matches_tag( $version, $tag ) { return version_compare( $normalized_tag, $normalized_version, '==' ); } + /** + * Check the plugin's Requires at least and Requires PHP headers. + * + * WordPress enforces these headers from the main plugin file, so unlike the readme fields + * they can't be ignored when invalid. Warn when a header isn't a valid version, is higher + * than the readme allows, or disagrees with the readme. + * + * @param object $headers The plugin headers, with `RequiresWP` and `RequiresPHP` properties. + * @param object $readme The parsed readme, with `requires` and `requires_php` properties. + * @return array Import warnings, keyed by warning code. + */ + public static function requires_header_warnings( $headers, $readme ) { + $warnings = []; + + // `6.0` and `6.0.0` are the same requirement. + $normalize = static function ( $v ) { + return preg_replace( '/(\.0)+$/', '', $v ); + }; + + $stable_branch = defined( 'WP_CORE_STABLE_BRANCH' ) ? WP_CORE_STABLE_BRANCH : $GLOBALS['wp_version']; + + $requires = $headers->RequiresWP ?? ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Plugin header key. + if ( $requires ) { + // Same rules the readme parser applies to its Requires at least field. + if ( ! preg_match( '!^\d+\.\d(\.\d+)?$!', $requires ) || (float) $requires > round( (float) $stable_branch + 0.1, 1 ) ) { + $warnings['plugin_requires_header_invalid'] = $requires; + } elseif ( ! empty( $readme->requires ) && $normalize( $requires ) !== $normalize( $readme->requires ) ) { + $warnings['requires_header_mismatch'] = [ + 'plugin' => $requires, + 'readme' => $readme->requires, + ]; + } + } + + $requires_php = $headers->RequiresPHP ?? ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Plugin header key. + if ( $requires_php ) { + if ( ! preg_match( '!^\d+(\.\d+){1,2}$!', $requires_php ) ) { + $warnings['plugin_requires_php_header_invalid'] = $requires_php; + } elseif ( ! empty( $readme->requires_php ) && $normalize( $requires_php ) !== $normalize( $readme->requires_php ) ) { + $warnings['requires_php_header_mismatch'] = [ + 'plugin' => $requires_php, + 'readme' => $readme->requires_php, + ]; + } + } + + return $warnings; + } + /** * Add support for additional plugin headers prior to WordPress supporting it. * diff --git a/wordpress.org/public_html/wp-content/plugins/plugin-directory/readme/class-parser.php b/wordpress.org/public_html/wp-content/plugins/plugin-directory/readme/class-parser.php index 3fd4b782fe..84b88b7e4d 100644 --- a/wordpress.org/public_html/wp-content/plugins/plugin-directory/readme/class-parser.php +++ b/wordpress.org/public_html/wp-content/plugins/plugin-directory/readme/class-parser.php @@ -867,7 +867,7 @@ protected function sanitize_requires_version( $version ) { // x.y or x.y.z ! preg_match( '!^\d+\.\d(\.\d+)?$!', $version ) || // Allow plugins to mark themselves as requireing Stable+0.1 (trunk/master) but not higher - defined( 'WP_CORE_STABLE_BRANCH' ) && ( (float)$version > (float)WP_CORE_STABLE_BRANCH+0.1 ) + ( defined( 'WP_CORE_STABLE_BRANCH' ) && (float) $version > round( (float) WP_CORE_STABLE_BRANCH + 0.1, 1 ) ) ) { $this->warnings['requires_header_ignored'] = true; // Ignore the readme value. diff --git a/wordpress.org/public_html/wp-content/plugins/plugin-directory/readme/class-validator.php b/wordpress.org/public_html/wp-content/plugins/plugin-directory/readme/class-validator.php index 70d73c5fd6..04ffe23865 100644 --- a/wordpress.org/public_html/wp-content/plugins/plugin-directory/readme/class-validator.php +++ b/wordpress.org/public_html/wp-content/plugins/plugin-directory/readme/class-validator.php @@ -335,6 +335,36 @@ public function translate_code_to_message( $error_code, $data = false ) { __( 'The %s field is missing. It should be defined here, or in your main plugin file.', 'wporg-plugins' ), 'Requires PHP' ); + case 'plugin_requires_header_invalid': + $latest_wordpress_version = defined( 'WP_CORE_STABLE_BRANCH' ) ? WP_CORE_STABLE_BRANCH : '6.5'; + + return sprintf( + /* translators: 1: plugin header tag; 2: the header value; 3: Example version 5.0. 4: Example version 4.9. */ + __( 'The %1$s header in your main plugin file (%2$s) is not a valid WordPress version. WordPress checks this header before activating your plugin, so it should only contain a WordPress version such as %3$s or %4$s.', 'wporg-plugins' ), + 'Requires at least', + '' . esc_html( $data ) . '', + '' . number_format( $latest_wordpress_version, 1 ) . '', + '' . number_format( $latest_wordpress_version - 0.1, 1 ) . '' + ); + case 'plugin_requires_php_header_invalid': + global $required_php_version; // WP wp-includes/version.php. + return sprintf( + /* translators: 1: plugin header tag; 2: the header value; 3: Example version 7.0. 4: Example version 8.2. */ + __( 'The %1$s header in your main plugin file (%2$s) is not a valid PHP version. WordPress checks this header before activating your plugin, so it should only contain a PHP version such as %3$s or %4$s.', 'wporg-plugins' ), + 'Requires PHP', + '' . esc_html( $data ) . '', + '' . esc_html( $required_php_version ?? '7.0' ) . '', + '8.2' + ); + case 'requires_header_mismatch': + case 'requires_php_header_mismatch': + return sprintf( + /* translators: 1: plugin header tag; 2: the value in the main plugin file; 3: the value in the readme. */ + __( 'The %1$s header in your main plugin file (%2$s) does not match the one in your readme (%3$s). The value from your main plugin file is used, so update your readme to match.', 'wporg-plugins' ), + '' . ( 'requires_header_mismatch' === $error_code ? 'Requires at least' : 'Requires PHP' ) . '', + '' . esc_html( is_array( $data ) ? ( $data['plugin'] ?? '' ) : '' ) . '', + '' . esc_html( is_array( $data ) ? ( $data['readme'] ?? '' ) : '' ) . '' + ); case 'stable_tag_invalid': return sprintf( /* translators: 1: 'Stable tag', 2: /trunk/ SVN directory */ diff --git a/wordpress.org/public_html/wp-content/plugins/plugin-directory/tests/Import_Requires_Header_Warnings_Test.php b/wordpress.org/public_html/wp-content/plugins/plugin-directory/tests/Import_Requires_Header_Warnings_Test.php new file mode 100644 index 0000000000..c3b1f77c16 --- /dev/null +++ b/wordpress.org/public_html/wp-content/plugins/plugin-directory/tests/Import_Requires_Header_Warnings_Test.php @@ -0,0 +1,114 @@ +assertSame( $expected, Import::requires_header_warnings( (object) $headers, (object) $readme ) ); + } + + /** + * Supplies plugin headers, readme fields, and the warnings they should produce. + * + * @return array> + */ + public static function headers_provider(): array { + return array( + 'matching, with trailing .0' => array( + array( + 'RequiresWP' => '6.0', + 'RequiresPHP' => '7.4', + ), + array( + 'requires' => '6.0.0', + 'requires_php' => '7.4', + ), + array(), + ), + 'no readme fields to compare' => array( + array( + 'RequiresWP' => '6.0', + 'RequiresPHP' => '7.4', + ), + array( + 'requires' => '', + 'requires_php' => '', + ), + array(), + ), + 'header newer than trunk' => array( + array( + 'RequiresWP' => '99.0', + 'RequiresPHP' => '', + ), + array( + 'requires' => '6.0', + 'requires_php' => '', + ), + array( 'plugin_requires_header_invalid' => '99.0' ), + ), + 'headers not versions' => array( + array( + 'RequiresWP' => '6.0-beta', + 'RequiresPHP' => '8', + ), + array( + 'requires' => '', + 'requires_php' => '', + ), + array( + 'plugin_requires_header_invalid' => '6.0-beta', + 'plugin_requires_php_header_invalid' => '8', + ), + ), + 'headers differ from readme' => array( + array( + 'RequiresWP' => '6.2', + 'RequiresPHP' => '8.0', + ), + array( + 'requires' => '6.0', + 'requires_php' => '7.4', + ), + array( + 'requires_header_mismatch' => array( + 'plugin' => '6.2', + 'readme' => '6.0', + ), + 'requires_php_header_mismatch' => array( + 'plugin' => '8.0', + 'readme' => '7.4', + ), + ), + ), + ); + } +}