Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin: Add requirement for minimum and maximum WordPress versions #35194

Open
wants to merge 17 commits into
base: trunk
Choose a base branch
from
Open
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
266 changes: 246 additions & 20 deletions gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,203 @@
defined( 'GUTENBERG_DEVELOPMENT_MODE' ) or define( 'GUTENBERG_DEVELOPMENT_MODE', true );
### END AUTO-GENERATED DEFINES

gutenberg_pre_init();
// Minimum supported version. Has to match "Requires at least" header above.
if ( ! defined( 'GUTENBERG_MIN_WP_VERSION' ) ) {
define( 'GUTENBERG_MIN_WP_VERSION', '6.2' );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the min version always known for each Gutenberg release?

What if the plan changes and thus that version might be different (maybe older such as a previous minor or later)? Later isn't necessarily problematic as the remedy is to upgrade. Older is problematic in that a fatal error might happen.

Wondering if instead, this minimum version should be set by Core and consumed in the plugin. For example, Core could provide a global function such as _get_plugin_wp_min_compatible_version() where for-core plugins like Gutenberg could invoke. Let Core determine which version is the min compatible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh, sorry but I think I'm missing something? Does that mean we're getting rid of the "Requires at least" plugin header?

What if the plan changes and thus that version might be different

That means a plugin will not "know" what is the minimum WP version it supports at release, and want to change/fix it later? This hasn't been a problem... forever, not sure it is a problem now?

Wondering if instead, this minimum version should be set by Core...
...
Let Core determine which version is the min compatible.

Not sure how core would "know" if a plugin is fully compatible or not. Emphasis on "fully compatible" here as some incompatibilities are not obvious or easy to detect.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, this isn't correct. Sorry. It's a minimum version in Core, but a max version in the plugin.

Not sure how core would "know" if a plugin is fully compatible or not. Emphasis on "fully compatible" here as some incompatibilities are not obvious or easy to detect.

Historically within Core itself, it views the minimum compatible version of the GB plugin to be the first version that causes a fatal error. That's very different from defining "fully compatible". It's focused on preventing fatal errors for users.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically the versions that are compatible with the WordPress version in development aren't known until close to WP Beta 1.

Just before Beta 1 or during beta is when the harded code GB version is updated Core (currently within _upgrade_core_deactivate_incompatible_plugins()).

What's my point?

The plugin's actual maximum WordPress version might not be known at the plugin release time. It might change as the WordPress dev cycle advances into beta.

For example, a fatal error happened recently which then caused an update in Core to set the 17.6 as the minimum compat version for WP 6.5. I'm wondering if 17.5.x or older would have know it's max WP version is WP 6.4, and not 6.5?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can released GB plugin versions know if Core itself will deactivate it due to be incompatible?

That's where the plugin could use the proposed Core function _get_plugin_wp_min_compatible_version().

Using the previous example, that Core function would return 17.6. So then released plugin versions could invoke the Core function and take action itself. Then in time once all released versions of the plugin have this new code, Core would no longer need to deactivate the plugin, i.e. because the plugin handles it itself.

Copy link
Contributor Author

@azaozz azaozz Feb 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, this isn't correct. Sorry. It's a minimum version in Core, but a max version in the plugin.

Ahh I see. I knew I'm missing something :)

How can released GB plugin versions know if Core itself will deactivate it due to be incompatible?

The same way it "knows" it is not going to be installed on a server with a PHP version lower than the minimum required, and in a "too old" WP? I.e. the plugin sets its own requirements, and WP makes sure these requirements are met. Same as PHP and WP versions, and now plugin dependencies.

Similar logic has existed in core since 2007 (WP 2.0) I think: plugins can specify minimal WP version. This is opt-in and enforced by the plugins repo API and by the core plugins API.

Adding another optional requirement for a maximum supported WP version would be best to follow the same logic/design imho, and should be available for all plugins. I know of 2-3 other plugins that can use this right now, for example the Test jQuery Updates plugin that will probably be used again for jQuery 4.0.

That's where the plugin could use the proposed Core function _get_plugin_wp_min_compatible_version().

Hmm, don't think WP (core) would be able to "know better" which plugin is compatible with which version. This info should be in the plugin, and is set to a constant in this PR. So not sure why a plugin will have to use a WP function to get the info it sets itself? Think it is the opposite: WP should "read" what the plugin requirements are and check if they are satisfied. If not satisfied WP should not load the plugin and should warn the users/site admins.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plugin's actual maximum WordPress version might not be known at the plugin release time. It might change as the WordPress dev cycle advances into beta.

Right. For that reason the "maximum supported" WP version can only be a major (two digits) version and should (generally) match the "tested up to" plugin header.

Minor version of Gutenberg will still support the same "max WP" version. In addition the "max WP" version will include support for the next "WP Trunk", (alpha, beta, RC). This is needed in order to be able to use the current Gutenberg releases in a WP Trunk installs, like on wp.org. Please see the discussion above.

}

// Max supported version.
if ( ! defined( 'GUTENBERG_MAX_WP_VERSION' ) ) {
/*
* The maximum WordPress version that is supported.
*
* This constant should be set to a major (two digits) version. For example,
* whether the current WP version is 6.4-RC2-57007-src, 6.4, 6.4.1-RC1, or 6.4.2
* the constant would be 6.4.
*
* Generally that would be the currently released WordPress version if the WP development
* cycle is in alpha or (early) beta, or the next WordPress version if development is in RC.
* In nearly all cases that would be the two digits version of the latest WP branch,
* see: https://core.trac.wordpress.org/browser/branches.
* The expectation is that the new features, improvements and bug fixes from the current
* Gutenberg version will be synced with the current development WordPress version (alpha, beta)
* before it is released.
*
* If WordPress is in late beta ("late" as in few days before RC), and there are features
* and/or improvements in the current Gutenberg version that will not be synced with this
* WordPress version, the maximum supported WP version should be set to the next release version,
* i.e. the current WordPress development version. Generally in this case, late beta,
* WordPress wouldn't have been branched yet.
* However if WP is currently in RC, it most likely would have been branched already so using
* the version of the latest branch as described above would work as expected.
*
* For example: Currently WordPress 6.4 is in RC. It was branched and development continues
* in the 6.4 branch. At the same time development of WordPress 6.5 has already started in trunk.
* So the max supported WP version for the next Gutenberg release should be set to 6.4
* (same as the latest WP branch). The expectation is that any new features in this Gutenberg
* release will be synced/merged to WP 6.5 so this versions of the Gutenberg plugin
* should not be used there.
*/
define( 'GUTENBERG_MAX_WP_VERSION', '6.4' );
}

if ( defined( 'ABSPATH' ) ) {
gutenberg_pre_init();
} else {
die( 'Invalid request.' );
}

/**
* Retiurns the text for the "WordPress version is too old" notices.
*
* @since 17.2.0
*
* @return string Text for the notices, escaped.
*/
function gutenberg_wordpress_version_too_old_text() {
if ( current_user_can( 'update_plugins' ) ) {
$text = sprintf(
/* translators: %s: Minimum required version */
__( 'Gutenberg requires WordPress %s or later to function properly. It was disabled to prevent errors. Please upgrade WordPress.', 'gutenberg' ),
GUTENBERG_MIN_WP_VERSION
);
} else {
$text = sprintf(
/* translators: %s: Minimum required version */
__( 'Gutenberg requires WordPress %s or later to function properly. It was disabled to prevent errors. Please ask an administrator to upgrade WordPress.', 'gutenberg' ),
GUTENBERG_MIN_WP_VERSION
);
}

return esc_html( $text );
}

/**
* Retiurns the text for the "Gutenberg version is too old" notices.
*
* @since 17.2.0
*
* @return string Text for the notices, escaped.
*/
function gutenberg_version_too_old_text() {
if ( current_user_can( 'update_plugins' ) ) {
$text = __( 'The Gutenberg plugin cannot be used. It is too old for your version of WordPress and was disabled to prevent errors. Please update it.', 'gutenberg' );
} else {
$text = __( 'The Gutenberg plugin cannot be used. It is too old for your version of WordPress and was disabled to prevent errors. Please ask an administrator to update it.', 'gutenberg' );
}

return esc_html( $text );
}

/**
* Display a version notice and deactivate the Gutenberg plugin.
* Display a "WordPress version is too old" notice.
*
* @since 0.1.0
*/
function gutenberg_wordpress_version_notice() {
function gutenberg_wordpress_version_too_old_notice() {
$current_screen = get_current_screen();

// Show only on the Dashboard and the Plugins screen.
if ( ! $current_screen || ( 'dashboard' !== $current_screen->id && 'plugins' !== $current_screen->id ) ) {
return;
}

echo '<div class="error"><p>';
/* translators: %s: Minimum required version */
printf( __( 'Gutenberg requires WordPress %s or later to function properly. Please upgrade WordPress before activating Gutenberg.', 'gutenberg' ), '5.9' );
echo gutenberg_wordpress_version_too_old_text();
echo '</p></div>';
}

/**
* Display a "Gutenberg version is too old" notice.
*
* @since 17.2.0
*/
function gutenberg_version_too_old_notice() {
$current_screen = get_current_screen();

// Show only on the Dashboard and the Plugins screen.
if ( ! $current_screen || ( 'dashboard' !== $current_screen->id && 'plugins' !== $current_screen->id ) ) {
return;
}

echo '<div class="error"><p>';
echo gutenberg_version_too_old_text();
echo '</p></div>';
}

/**
* Add a "WordPress version is too old" plugins list table notice.
*
* @since 17.2.0
*
* @param string[] $plugin_meta Array of plugin row meta data.
* @param string $file Path to the plugin file relative to the plugins directory.
* @return string[] Updated array of plugin row meta data.
*/
function gutenberg_wordpress_version_too_old_plugin_row_meta( $plugin_meta, $file ) {
$plugin_basename = basename( __DIR__ ) . '/gutenberg.php';

deactivate_plugins( array( 'gutenberg/gutenberg.php' ) );
if ( $file === $plugin_basename ) {
// Prevent PHP warnings when a plugin uses this filter incorrectly.
$plugin_meta = (array) $plugin_meta;

// The text is already HTML escaped.
$text = gutenberg_wordpress_version_too_old_text();

$plugin_meta['gutenberg-plugin-notice'] = '<p style="color:red;margin:0.7em 0;">' . $text . '</p>';
}

return $plugin_meta;
}

/**
* Add a "Gutenberg version is too old" plugins list table notice.
*
* @since 17.2.0
*
* @param string[] $plugin_meta Array of plugin row meta data.
* @param string $file Path to the plugin file relative to the plugins directory.
* @return string[] Updated array of plugin row meta data.
*/
function gutenberg_version_too_old_plugin_row_meta( $plugin_meta, $file ) {
$plugin_basename = basename( __DIR__ ) . '/gutenberg.php';

if ( $file === $plugin_basename ) {
// Prevent PHP warnings when a plugin uses this filter incorrectly.
$plugin_meta = (array) $plugin_meta;

// The text is already HTML escaped.
$text = gutenberg_version_too_old_text();

$plugin_meta['gutenberg-plugin-notice'] = '<p style="color:red;margin:0.7em 0;">' . $text . '</p>';
}

return $plugin_meta;
}

/**
* Add a "(disabled)" notice to the plugin's action links.
*
* @since 17.2.0
*
* @param string[] $links Array of plugin action links.
* @param string $file Path to the plugin file relative to the plugins directory.
* @return string[] Updated array of plugin action links.
*/
function gutenberg_disabled_action_links_notice( $links, $file ) {
$plugin_basename = basename( __DIR__ ) . '/gutenberg.php';

if ( $file === $plugin_basename ) {
// Prevent PHP warnings when a plugin uses this filter incorrectly.
$links = (array) $links;

$links = array_merge(
array( 'gutenberg-plugin-notice' => __( '(disabled)', 'gutenberg' ) ),
$links
);
}

return $links;
}

/**
Expand All @@ -44,30 +227,73 @@ function gutenberg_build_files_notice() {
}

/**
* Verify that we can initialize the Gutenberg editor , then load it.
* Verify that we can initialize the Gutenberg editor, then load it.
*
* @since 1.5.0
*/
function gutenberg_pre_init() {
global $wp_version;
if ( defined( 'GUTENBERG_DEVELOPMENT_MODE' ) && GUTENBERG_DEVELOPMENT_MODE && ! file_exists( __DIR__ . '/build/blocks' ) ) {
add_action( 'admin_notices', 'gutenberg_build_files_notice' );
return;
}

// Get unmodified $wp_version.
include ABSPATH . WPINC . '/version.php';

// Strip '-src' from the version string. Messes up version_compare().
$version = str_replace( '-src', '', $wp_version );
// Strip '-src' from the WP version string. May interfere with `version_compare()`.
$wp_version = str_replace( '-src', '', $wp_version );

/*
* Note that `version_compare()` considers 1 < 1.0 < 1.0.0 (before suffixes)
* so 6.0 < 6.0.0-alpha and 6.0-beta < 6.0.0-alpha are both true.
* To work around any possible problems when comparing with WP development version strings
* the GUTENBERG_MAX_WP_VERSION is incremented by one and the comparisons are with `>=`.
*/
$max_supported_version = (float) GUTENBERG_MAX_WP_VERSION + 0.1;

// `version_compare()` expects strings.
$max_supported_version = (string) $max_supported_version;

// Check if this version of Gutenberg supports the version of WordPress.
if ( version_compare( $wp_version, GUTENBERG_MIN_WP_VERSION, '<' ) ) {
add_action( 'admin_notices', 'gutenberg_wordpress_version_too_old_notice' );

// Compare against major release versions (X.Y) rather than minor (X.Y.Z)
// unless a minor release is the actual minimum requirement. WordPress reports
// X.Y for its major releases.
if ( version_compare( $version, '5.9', '<' ) ) {
add_action( 'admin_notices', 'gutenberg_wordpress_version_notice' );
// Also add a notice to the plugin's row in the plugins list tables.
add_filter( 'plugin_row_meta', 'gutenberg_wordpress_version_too_old_plugin_row_meta', 10, 2 );

// Add "(disabled)" notice to the plugins action links.
add_filter( 'plugin_action_links', 'gutenberg_disabled_action_links_notice', 10, 2 );
add_filter( 'network_admin_plugin_action_links', 'gutenberg_disabled_action_links_notice', 10, 2 );

// Return early, do not load Gutenberg.
return;
azaozz marked this conversation as resolved.
Show resolved Hide resolved
} elseif ( version_compare( $wp_version, $max_supported_version, '>=' ) ) {
/*
* Do not load Gutenebrg in newer, unsupported versions of WordPress,
* however allow it to load in the next development version.
* Example: If GUTENBERG_MAX_WP_VERSION is 6.4, the plugin will load
* in 6.5 alpha, beta, and RC, but not in the released 6.5.0.
* This will prevent incompatibilities, PHP fatal errors, etc. in production
* while still allowing testing with the WP develipment versions.
* When loading is prevented ask the users to update Gutenberg.
*/
add_action( 'admin_notices', 'gutenberg_version_too_old_notice' );

// Also add a notice to the plugin's row in the plugins list table.
add_filter( 'plugin_row_meta', 'gutenberg_version_too_old_plugin_row_meta', 10, 2 );

// Add "(disabled)" notice to the plugins action links.
add_filter( 'plugin_action_links', 'gutenberg_disabled_action_links_notice', 10, 2 );
add_filter( 'network_admin_plugin_action_links', 'gutenberg_disabled_action_links_notice', 10, 2 );

return;
}

// Check if Gutenberg has been built for the first time.
if (
defined( 'GUTENBERG_DEVELOPMENT_MODE' ) &&
GUTENBERG_DEVELOPMENT_MODE &&
! file_exists( __DIR__ . '/build/blocks' )
) {
add_action( 'admin_notices', 'gutenberg_build_files_notice' );
return;
}

// Load the plugin.
require_once __DIR__ . '/lib/load.php';
}
Loading