Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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 ) ) {
Comment thread
obenland marked this conversation as resolved.
Resolved
$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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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' ),
'<code>Requires PHP</code>'
);
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' ),
'<code>Requires at least</code>',
'<code>' . esc_html( $data ) . '</code>',
'<code>' . number_format( $latest_wordpress_version, 1 ) . '</code>',
'<code>' . number_format( $latest_wordpress_version - 0.1, 1 ) . '</code>'
);
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' ),
'<code>Requires PHP</code>',
'<code>' . esc_html( $data ) . '</code>',
'<code>' . esc_html( $required_php_version ?? '7.0' ) . '</code>',
'<code>8.2</code>'
);
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' ),
'<code>' . ( 'requires_header_mismatch' === $error_code ? 'Requires at least' : 'Requires PHP' ) . '</code>',
'<code>' . esc_html( is_array( $data ) ? ( $data['plugin'] ?? '' ) : '' ) . '</code>',
'<code>' . esc_html( is_array( $data ) ? ( $data['readme'] ?? '' ) : '' ) . '</code>'
);
case 'stable_tag_invalid':
return sprintf(
/* translators: 1: 'Stable tag', 2: /trunk/ SVN directory */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php
/**
* Tests for Import::requires_header_warnings().
*
* @package WordPressdotorg\Plugin_Directory\Tests
*/

declare( strict_types = 1 );

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
use WordPressdotorg\Plugin_Directory\CLI\Import;

/**
* Covers the import warnings for the Requires at least and Requires PHP plugin headers.
*
* @group import
*/
#[Group( 'import' )]
class Import_Requires_Header_Warnings_Test extends TestCase {

/**
* The plugin headers are checked on their own and against the readme.
*
* @dataProvider headers_provider
*
* @param array $headers The RequiresWP and RequiresPHP plugin headers.
* @param array $readme The readme's requires and requires_php fields.
* @param array $expected The expected warnings.
* @return void
*/
#[DataProvider( 'headers_provider' )]
public function test_requires_header_warnings( array $headers, array $readme, array $expected ): void {
$this->assertSame( $expected, Import::requires_header_warnings( (object) $headers, (object) $readme ) );
}

/**
* Supplies plugin headers, readme fields, and the warnings they should produce.
*
* @return array<string, array<int, 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',
),
),
),
);
}
}
Loading