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',
+ ),
+ ),
+ ),
+ );
+ }
+}